@deepnoid/canvas 0.1.20 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,29 +8,29 @@ const AnnotatedCanvas = ({ image, coordinates, panZoomable = false, ZoomButton,
8
8
  const canvasRef = useRef(null);
9
9
  const [displayCoordinates, setDisplayCoordinates] = useState();
10
10
  const { image: loadedImage, isLoading, error } = useImageLoader(image, 10000, onImageLoadSuccess, onImageLoadError);
11
- const { moveX, moveY, zoom, zoomX, zoomY, dx, dy, dw, dh, handleWheel, handleMouseDown, handleMouseMove, handleMouseUp, handleMouseLeave, resetView, } = usePanZoom({
11
+ const { moveX, moveY, zoom, initZoom, zoomX, zoomY, dx, dy, dw, dh, handleWheel, handleMouseDown, handleMouseMove, handleMouseUp, handleMouseLeave, initCanvas, } = usePanZoom({
12
12
  canvasRef,
13
13
  image: loadedImage,
14
14
  panZoomable,
15
15
  resetOnImageChange,
16
16
  });
17
+ const loaded = loadedImage && !isLoading;
17
18
  useEffect(() => {
18
- if (loadedImage && !isLoading) {
19
+ if (loaded) {
19
20
  setDisplayCoordinates(coordinates);
20
21
  }
21
- }, [loadedImage, isLoading, coordinates]);
22
+ }, [loaded, coordinates]);
22
23
  useEffect(() => {
23
24
  if (isLoading) {
24
25
  setDisplayCoordinates(undefined);
25
26
  }
26
27
  }, [isLoading]);
27
- if (!image || image.trim() === '' || error) {
28
+ if (!image || error)
28
29
  return null;
29
- }
30
30
  return (_jsx("div", { style: { width: '100%', height: '100%', display: 'flex', flex: 1 }, children: _jsx("div", { onWheel: handleWheel, onMouseMove: handleMouseMove, onMouseDown: handleMouseDown, onMouseUp: handleMouseUp, onMouseLeave: handleMouseLeave, style: {
31
31
  flex: 1,
32
32
  position: 'relative',
33
33
  cursor: panZoomable ? 'grab' : 'default',
34
- }, children: loadedImage && !isLoading && (_jsxs(_Fragment, { children: [_jsx("canvas", { ref: canvasRef, style: { position: 'absolute', width: '100%', height: '100%', left: 0, top: 0 } }), _jsx(Canvas, { moveX: moveX, moveY: moveY, zoomX: zoomX, zoomY: zoomY, zoom: zoom, dx: dx, dy: dy, dw: dw, dh: dh, coordinates: displayCoordinates, editable: editable }), ZoomButton && _jsx(ZoomButton, { onClick: resetView, children: `${Math.round(zoom * 100)}%` })] })) }) }));
34
+ }, children: loaded && (_jsxs(_Fragment, { children: [_jsx("canvas", { ref: canvasRef, style: { position: 'absolute', width: '100%', height: '100%', left: 0, top: 0 } }), _jsx(Canvas, { moveX: moveX, moveY: moveY, zoomX: zoomX, zoomY: zoomY, zoom: zoom, dx: dx, dy: dy, dw: dw, dh: dh, coordinates: displayCoordinates, editable: editable }), ZoomButton && initZoom > 0 && (_jsx(ZoomButton, { onClick: initCanvas, children: `${Math.round((zoom / initZoom) * 100)}%` }))] })) }) }));
35
35
  };
36
36
  export default AnnotatedCanvas;
@@ -9,17 +9,18 @@ export declare const usePanZoom: ({ canvasRef, image, panZoomable, resetOnImageC
9
9
  moveX: number;
10
10
  moveY: number;
11
11
  zoom: number;
12
+ initZoom: number;
12
13
  zoomX: number;
13
14
  zoomY: number;
14
15
  dx: number;
15
16
  dy: number;
16
17
  dw: number;
17
18
  dh: number;
18
- handleWheel: (e: WheelEvent) => void;
19
- handleMouseDown: (e: MouseEvent) => void;
20
- handleMouseMove: (e: MouseEvent) => void;
21
- handleMouseUp: () => void;
22
- handleMouseLeave: () => void;
23
- resetView: () => void;
19
+ handleWheel: (event: WheelEvent) => void;
20
+ handleMouseDown: (event: MouseEvent) => false | undefined;
21
+ handleMouseMove: (event: MouseEvent) => void;
22
+ handleMouseUp: (event: MouseEvent) => void;
23
+ handleMouseLeave: (event: MouseEvent) => void;
24
+ initCanvas: () => void;
24
25
  };
25
26
  export {};
@@ -1,211 +1,181 @@
1
1
  'use client';
2
2
  import { useCallback, useEffect, useState, useRef } from 'react';
3
- import { resolutionCanvas, drawCanvas, getMousePointTransform } from '../utils/graphic';
3
+ import { resolutionCanvas, drawCanvas, getMousePointTransform, canvasCenterPoint } from '../utils/graphic';
4
4
  import { MouseStatus } from '../enum/common';
5
+ import useResizeObserver from './useResizeObserver';
5
6
  export const usePanZoom = ({ canvasRef, image, panZoomable = false, resetOnImageChange = true }) => {
6
- const [moveX, setMoveX] = useState(0);
7
- const [moveY, setMoveY] = useState(0);
8
- const [zoom, setZoom] = useState(1);
7
+ const ZOOM_UNIT = 0.9;
8
+ const MAX_ZOOM = 4;
9
+ const MIN_ZOOM = 0.5;
10
+ const { width, height } = useResizeObserver({ ref: canvasRef });
11
+ const [initZoom, setInitZoom] = useState(0);
12
+ const [zoom, setZoom] = useState(0);
9
13
  const [zoomX, setZoomX] = useState(0);
10
14
  const [zoomY, setZoomY] = useState(0);
11
15
  const [dx, setDx] = useState(0);
12
16
  const [dy, setDy] = useState(0);
13
17
  const [dw, setDw] = useState(0);
14
18
  const [dh, setDh] = useState(0);
15
- const [status, setStatus] = useState();
16
- const [startMousePoint, setStartMousePoint] = useState();
17
- const animationFrameRef = useRef(null);
18
- const currentTransformRef = useRef({
19
- moveX: 0,
20
- moveY: 0,
21
- zoom: 1,
22
- zoomX: 0,
23
- zoomY: 0,
24
- dx: 0,
25
- dy: 0,
26
- });
27
- const ZOOM_UNIT = 0.9;
28
- const MAX_ZOOM = 4;
29
- const MIN_ZOOM = 0.5;
30
- const prevImageRef = useRef(null);
31
- const immediateRender = useCallback((moveX, moveY, zoomX, zoomY, zoom, dx, dy) => {
32
- const canvasEl = canvasRef.current;
33
- if (!canvasEl || !image)
34
- return;
35
- const resolvedCanvas = resolutionCanvas(canvasEl);
36
- if (!resolvedCanvas)
37
- return;
38
- drawCanvas(moveX, moveY, zoomX, zoomY, zoom, dx, dy, resolvedCanvas, image, true);
39
- }, [canvasRef, image]);
40
- const scheduleRender = useCallback(() => {
41
- if (animationFrameRef.current) {
42
- cancelAnimationFrame(animationFrameRef.current);
19
+ const [moveX, setMoveX] = useState(0);
20
+ const [moveY, setMoveY] = useState(0);
21
+ const [status, setStatus] = useState('');
22
+ const [imageOnloadCount, setImageOnloadCount] = useState(0);
23
+ const [_imageInfo, _setImageInfo] = useState();
24
+ const imageRef = useRef(new Image());
25
+ const calculatorZoomPoint = useCallback((canvasEl, movementX, movementY, dx, dy) => {
26
+ let x = 0;
27
+ let y = 0;
28
+ if (canvasRef.current) {
29
+ x = -dx - movementX + canvasEl.width / 2;
30
+ y = -dy - movementY + canvasEl.height / 2;
43
31
  }
44
- animationFrameRef.current = requestAnimationFrame(() => {
45
- const current = currentTransformRef.current;
46
- immediateRender(current.moveX, current.moveY, current.zoomX, current.zoomY, current.zoom, current.dx, current.dy);
47
- });
48
- }, [immediateRender]);
49
- const initCanvas = useCallback((img) => {
50
- const canvasEl = canvasRef.current;
51
- if (!canvasEl)
52
- return;
53
- const canvasWidth = canvasEl.clientWidth;
54
- const canvasHeight = canvasEl.clientHeight;
55
- let scale = 1;
56
- if (img.width > canvasWidth || img.height > canvasHeight) {
57
- scale = Math.min(canvasWidth / img.width, canvasHeight / img.height);
32
+ return { x, y };
33
+ }, []);
34
+ const calculateInitZoom = useCallback((image, canvasEl) => {
35
+ if (canvasEl.clientWidth < canvasEl.clientHeight) {
36
+ return canvasEl.clientWidth / canvasEl.clientHeight < image.width / image.height
37
+ ? canvasEl.clientWidth / image.width
38
+ : canvasEl.clientHeight / image.height;
58
39
  }
59
- const dw = img.width * scale;
60
- const dh = img.height * scale;
61
- const dx = (canvasWidth - dw) / 2;
62
- const dy = (canvasHeight - dh) / 2;
63
- setDx(dx);
64
- setDy(dy);
65
- setDw(dw);
66
- setDh(dh);
67
- setZoom(scale);
68
- setZoomX(0);
69
- setZoomY(0);
40
+ else {
41
+ return canvasEl.clientWidth / canvasEl.clientHeight > image.width / image.height
42
+ ? canvasEl.clientHeight / image.height
43
+ : canvasEl.clientWidth / image.width;
44
+ }
45
+ }, []);
46
+ const init = (canvasEl, image) => {
47
+ const point = canvasCenterPoint(canvasEl, image);
48
+ setDx(point.x);
49
+ setDy(point.y);
50
+ setDw(image.width);
51
+ setDh(image.height);
70
52
  setMoveX(0);
71
53
  setMoveY(0);
72
- currentTransformRef.current = {
73
- moveX: 0,
74
- moveY: 0,
75
- zoom: scale,
76
- zoomX: 0,
77
- zoomY: 0,
78
- dx,
79
- dy,
80
- };
81
- }, [canvasRef]);
54
+ const init_zoom = calculateInitZoom(image, canvasEl);
55
+ setZoom(init_zoom);
56
+ setInitZoom(init_zoom);
57
+ const zoomPoint = calculatorZoomPoint(canvasEl, 0, 0, point.x, point.y);
58
+ setZoomX(zoomPoint.x);
59
+ setZoomY(zoomPoint.y);
60
+ };
61
+ const initCanvas = () => {
62
+ if (image) {
63
+ const canvasEl = resolutionCanvas(canvasRef.current);
64
+ if (canvasEl)
65
+ init(canvasEl, imageRef.current);
66
+ setImageOnloadCount((prev) => prev + 1);
67
+ }
68
+ };
82
69
  useEffect(() => {
83
- const canvasEl = canvasRef.current;
84
- if (!canvasEl || !image)
85
- return;
86
- const canvasWidth = canvasEl.clientWidth;
87
- const canvasHeight = canvasEl.clientHeight;
88
- const isTooBig = image.width > canvasWidth || image.height > canvasHeight;
89
- const isFirstLoad = !prevImageRef.current;
90
- if (resetOnImageChange || isFirstLoad) {
91
- initCanvas(image);
70
+ if (image) {
71
+ imageRef.current.src = image.src;
72
+ imageRef.current.onload = () => {
73
+ if (resetOnImageChange) {
74
+ const canvasEl = resolutionCanvas(canvasRef.current);
75
+ if (canvasEl)
76
+ init(canvasEl, imageRef.current);
77
+ }
78
+ setImageOnloadCount((prev) => prev + 1);
79
+ };
92
80
  }
93
- else if (isTooBig) {
94
- const scale = Math.min(canvasWidth / image.width, canvasHeight / image.height);
95
- const dw = image.width * scale;
96
- const dh = image.height * scale;
97
- const dx = (canvasWidth - dw) / 2;
98
- const dy = (canvasHeight - dh) / 2;
99
- setDw(dw);
100
- setDh(dh);
101
- setDx(dx);
102
- setDy(dy);
103
- currentTransformRef.current.dx = dx;
104
- currentTransformRef.current.dy = dy;
81
+ }, [image]);
82
+ const [startMousePoint, setStartMousePoint] = useState();
83
+ useEffect(() => {
84
+ const redraw = () => {
85
+ if (canvasRef.current) {
86
+ drawCanvas(moveX, moveY, zoomX, zoomY, zoom, dx, dy, resolutionCanvas(canvasRef.current), imageRef.current, true);
87
+ }
88
+ };
89
+ redraw();
90
+ }, [moveX, moveY, zoomX, zoomY, zoom, dx, dy, dw, dh, imageOnloadCount]);
91
+ useEffect(() => {
92
+ if (image) {
93
+ imageRef.current.src = image.src;
94
+ if (!resetOnImageChange) {
95
+ const canvasEl = resolutionCanvas(canvasRef.current);
96
+ if (canvasEl)
97
+ init(canvasEl, imageRef.current);
98
+ setImageOnloadCount((prev) => prev + 1);
99
+ }
105
100
  }
106
- prevImageRef.current = image;
107
- }, [image, resetOnImageChange, initCanvas, canvasRef]);
108
- const handleWheel = useCallback((e) => {
109
- const canvasEl = canvasRef.current;
110
- if (!panZoomable || !canvasRef.current || !canvasEl || !image)
111
- return;
112
- const currentZoom = currentTransformRef.current.zoom;
113
- let nextZoom = e.deltaY < 0 ? currentZoom / ZOOM_UNIT : currentZoom * ZOOM_UNIT;
114
- nextZoom = Math.min(Math.max(nextZoom, MIN_ZOOM), MAX_ZOOM);
115
- if ((currentZoom <= MIN_ZOOM && e.deltaY > 0) || (currentZoom >= MAX_ZOOM && e.deltaY < 0))
116
- return;
117
- const resolvedCanvas = resolutionCanvas(canvasEl);
118
- if (!resolvedCanvas)
101
+ }, [width, height]);
102
+ const handleWheel = (event) => {
103
+ if (!panZoomable)
119
104
  return;
120
- const resolution_ratio_x = resolvedCanvas.width / canvasEl.clientWidth;
121
- const resolution_ratio_y = resolvedCanvas.height / canvasEl.clientHeight;
122
- const current = currentTransformRef.current;
123
- const centerX = resolvedCanvas.width / 2 - current.dx * resolution_ratio_x - current.moveX * resolution_ratio_x;
124
- const centerY = resolvedCanvas.height / 2 - current.dy * resolution_ratio_y - current.moveY * resolution_ratio_y;
125
- currentTransformRef.current.zoom = nextZoom;
126
- currentTransformRef.current.zoomX = centerX;
127
- currentTransformRef.current.zoomY = centerY;
128
- immediateRender(current.moveX, current.moveY, centerX, centerY, nextZoom, current.dx, current.dy);
129
- setZoomX(centerX);
130
- setZoomY(centerY);
131
- setZoom(nextZoom);
132
- }, [canvasRef, panZoomable, image, immediateRender]);
133
- const handleMouseDown = useCallback((e) => {
134
- if (!panZoomable || !canvasRef.current)
105
+ if (canvasRef.current) {
106
+ let calc_zoom = event.deltaY < 0 ? (zoom || 1) * (1 / ZOOM_UNIT) : (zoom || 1) * ZOOM_UNIT;
107
+ if (initZoom * MAX_ZOOM < zoom * ZOOM_UNIT)
108
+ calc_zoom = calc_zoom * ZOOM_UNIT;
109
+ if (initZoom * MIN_ZOOM > zoom * ZOOM_UNIT)
110
+ calc_zoom = calc_zoom * (1 / ZOOM_UNIT);
111
+ const canvasEl = canvasRef.current;
112
+ const zoomPoint = calculatorZoomPoint(canvasEl, moveX, moveY, dx, dy);
113
+ setZoomX(zoomPoint.x);
114
+ setZoomY(zoomPoint.y);
115
+ setZoom(calc_zoom);
116
+ }
117
+ };
118
+ const handleMouseMove = (event) => {
119
+ if (!panZoomable)
135
120
  return;
121
+ if (status === MouseStatus.MOVE) {
122
+ if (canvasRef.current) {
123
+ const canvasEl = canvasRef.current;
124
+ const rect = canvasEl.getBoundingClientRect();
125
+ const mouseX = event.clientX - rect.left;
126
+ const mouseY = event.clientY - rect.top;
127
+ const mouse = getMousePointTransform({ x: mouseX, y: mouseY }, { x: moveX, y: moveY }, { x: zoomX, y: zoomY }, { x: dx, y: dy }, zoom || 1, canvasEl);
128
+ let x = mouse.x;
129
+ let y = mouse.y;
130
+ if (startMousePoint) {
131
+ x = x - startMousePoint.x;
132
+ y = y - startMousePoint.y;
133
+ }
134
+ setMoveX(moveX + x);
135
+ setMoveY(moveY + y);
136
+ const zoomPoint = calculatorZoomPoint(canvasEl, moveX + x, moveY + y, dx, dy);
137
+ setZoomX(zoomPoint.x);
138
+ setZoomY(zoomPoint.y);
139
+ }
140
+ }
141
+ event.preventDefault();
142
+ };
143
+ const handleMouseDown = (event) => {
144
+ if (!panZoomable)
145
+ return false;
136
146
  setStatus(MouseStatus.MOVE);
137
- const rect = canvasRef.current.getBoundingClientRect();
138
- const mouseX = e.clientX - rect.left;
139
- const mouseY = e.clientY - rect.top;
140
- const current = currentTransformRef.current;
141
- const transformed = getMousePointTransform({ x: mouseX, y: mouseY }, { x: current.moveX, y: current.moveY }, { x: current.zoomX, y: current.zoomY }, { x: current.dx, y: current.dy }, current.zoom, canvasRef.current);
142
- setStartMousePoint({ x: transformed.x, y: transformed.y });
143
- e.preventDefault();
144
- }, [canvasRef, panZoomable]);
145
- const handleMouseMove = useCallback((e) => {
146
- if (!panZoomable || status !== MouseStatus.MOVE || !canvasRef.current)
147
- return;
148
- const rect = canvasRef.current.getBoundingClientRect();
149
- const mouseX = e.clientX - rect.left;
150
- const mouseY = e.clientY - rect.top;
151
- const current = currentTransformRef.current;
152
- const transformed = getMousePointTransform({ x: mouseX, y: mouseY }, { x: current.moveX, y: current.moveY }, { x: current.zoomX, y: current.zoomY }, { x: current.dx, y: current.dy }, current.zoom, canvasRef.current);
153
- if (startMousePoint) {
154
- const deltaX = transformed.x - startMousePoint.x;
155
- const deltaY = transformed.y - startMousePoint.y;
156
- const newMoveX = current.moveX + deltaX;
157
- const newMoveY = current.moveY + deltaY;
158
- currentTransformRef.current.moveX = newMoveX;
159
- currentTransformRef.current.moveY = newMoveY;
160
- immediateRender(newMoveX, newMoveY, current.zoomX, current.zoomY, current.zoom, current.dx, current.dy);
161
- setMoveX(newMoveX);
162
- setMoveY(newMoveY);
147
+ if (canvasRef.current) {
148
+ const canvasEl = canvasRef.current;
149
+ const rect = canvasEl.getBoundingClientRect();
150
+ const mouseX = event.clientX - rect.left;
151
+ const mouseY = event.clientY - rect.top;
152
+ const mouse = getMousePointTransform({ x: mouseX, y: mouseY }, { x: moveX, y: moveY }, { x: zoomX, y: zoomY }, { x: dx, y: dy }, zoom || 1, canvasEl);
153
+ const x = mouse.x;
154
+ const y = mouse.y;
155
+ setStartMousePoint({
156
+ x: x,
157
+ y: y,
158
+ });
163
159
  }
164
- e.preventDefault();
165
- }, [canvasRef, panZoomable, status, startMousePoint, immediateRender]);
166
- const handleMouseUp = useCallback(() => {
160
+ event.preventDefault();
161
+ };
162
+ const handleMouseUp = (event) => {
167
163
  if (!panZoomable)
168
164
  return;
169
165
  setStatus(MouseStatus.STOP);
170
- }, [panZoomable]);
171
- const handleMouseLeave = useCallback(() => {
166
+ event.preventDefault();
167
+ };
168
+ const handleMouseLeave = (event) => {
172
169
  if (!panZoomable)
173
170
  return;
174
171
  setStatus(MouseStatus.STOP);
175
- }, [panZoomable]);
176
- useEffect(() => {
177
- if (!canvasRef.current || !image)
178
- return;
179
- if (dx !== 0 || dy !== 0 || zoom !== 1) {
180
- scheduleRender();
181
- }
182
- }, [canvasRef, image, moveX, moveY, zoomX, zoomY, zoom, dx, dy, scheduleRender]);
183
- useEffect(() => {
184
- currentTransformRef.current = {
185
- moveX,
186
- moveY,
187
- zoom,
188
- zoomX,
189
- zoomY,
190
- dx,
191
- dy,
192
- };
193
- }, [moveX, moveY, zoom, zoomX, zoomY, dx, dy]);
194
- const resetView = useCallback(() => {
195
- if (image)
196
- initCanvas(image);
197
- }, [image, initCanvas]);
198
- useEffect(() => {
199
- return () => {
200
- if (animationFrameRef.current) {
201
- cancelAnimationFrame(animationFrameRef.current);
202
- }
203
- };
204
- }, []);
172
+ event.preventDefault();
173
+ };
205
174
  return {
206
175
  moveX,
207
176
  moveY,
208
177
  zoom,
178
+ initZoom,
209
179
  zoomX,
210
180
  zoomY,
211
181
  dx,
@@ -217,6 +187,6 @@ export const usePanZoom = ({ canvasRef, image, panZoomable = false, resetOnImage
217
187
  handleMouseMove,
218
188
  handleMouseUp,
219
189
  handleMouseLeave,
220
- resetView,
190
+ initCanvas,
221
191
  };
222
192
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepnoid/canvas",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.cjs",