@deepnoid/canvas 0.1.19 → 0.1.21

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,134 +1,179 @@
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 ZOOM_UNIT = 0.9;
18
- const MAX_ZOOM = 4;
19
- const MIN_ZOOM = 0.5;
20
- const prevImageRef = useRef(null);
21
- const initCanvas = useCallback((img) => {
22
- const canvasEl = canvasRef.current;
23
- if (!canvasEl)
24
- return;
25
- const canvasWidth = canvasEl.clientWidth;
26
- const canvasHeight = canvasEl.clientHeight;
27
- let scale = 1;
28
- if (img.width > canvasWidth || img.height > canvasHeight) {
29
- scale = Math.min(canvasWidth / img.width, canvasHeight / img.height);
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;
31
+ }
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;
39
+ }
40
+ else {
41
+ return canvasEl.clientWidth / canvasEl.clientHeight > image.width / image.height
42
+ ? canvasEl.clientHeight / image.height
43
+ : canvasEl.clientWidth / image.width;
30
44
  }
31
- const dw = img.width * scale;
32
- const dh = img.height * scale;
33
- const dx = (canvasWidth - dw) / 2;
34
- const dy = (canvasHeight - dh) / 2;
35
- setDx(dx);
36
- setDy(dy);
37
- setDw(dw);
38
- setDh(dh);
39
- setZoom(scale);
40
- setZoomX(0);
41
- setZoomY(0);
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);
42
52
  setMoveX(0);
43
53
  setMoveY(0);
44
- }, [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
+ };
45
69
  useEffect(() => {
46
- const canvasEl = canvasRef.current;
47
- if (!canvasEl || !image)
48
- return;
49
- const canvasWidth = canvasEl.clientWidth;
50
- const canvasHeight = canvasEl.clientHeight;
51
- const isTooBig = image.width > canvasWidth || image.height > canvasHeight;
52
- const isFirstLoad = !prevImageRef.current;
53
- if (resetOnImageChange || isFirstLoad) {
54
- 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
+ };
55
80
  }
56
- else if (isTooBig) {
57
- const scale = Math.min(canvasWidth / image.width, canvasHeight / image.height);
58
- const dw = image.width * scale;
59
- const dh = image.height * scale;
60
- const dx = (canvasWidth - dw) / 2;
61
- const dy = (canvasHeight - dh) / 2;
62
- setDw(dw);
63
- setDh(dh);
64
- setDx(dx);
65
- setDy(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
+ const canvasEl = resolutionCanvas(canvasRef.current);
95
+ if (canvasEl)
96
+ init(canvasEl, imageRef.current);
97
+ setImageOnloadCount((prev) => prev + 1);
66
98
  }
67
- prevImageRef.current = image;
68
- }, [image, resetOnImageChange, initCanvas, canvasRef]);
69
- const handleWheel = useCallback((e) => {
70
- if (!panZoomable || !canvasRef.current || !image)
71
- return;
72
- const rect = canvasRef.current.getBoundingClientRect();
73
- const mouseX = e.clientX - rect.left;
74
- const mouseY = e.clientY - rect.top;
75
- let nextZoom = e.deltaY < 0 ? zoom / ZOOM_UNIT : zoom * ZOOM_UNIT;
76
- nextZoom = Math.min(Math.max(nextZoom, MIN_ZOOM), MAX_ZOOM);
77
- if ((zoom <= MIN_ZOOM && e.deltaY > 0) || (zoom >= MAX_ZOOM && e.deltaY < 0))
99
+ }, [width, height]);
100
+ const handleWheel = (event) => {
101
+ if (!panZoomable)
78
102
  return;
79
- setZoomX(mouseX);
80
- setZoomY(mouseY);
81
- setZoom(nextZoom);
82
- }, [canvasRef, panZoomable, zoom, image]);
83
- const handleMouseDown = useCallback((e) => {
84
- if (!panZoomable || !canvasRef.current)
103
+ if (canvasRef.current) {
104
+ let calc_zoom = event.deltaY < 0 ? (zoom || 1) * (1 / ZOOM_UNIT) : (zoom || 1) * ZOOM_UNIT;
105
+ if (initZoom * MAX_ZOOM < zoom * ZOOM_UNIT)
106
+ calc_zoom = calc_zoom * ZOOM_UNIT;
107
+ if (initZoom * MIN_ZOOM > zoom * ZOOM_UNIT)
108
+ calc_zoom = calc_zoom * (1 / ZOOM_UNIT);
109
+ const canvasEl = canvasRef.current;
110
+ const zoomPoint = calculatorZoomPoint(canvasEl, moveX, moveY, dx, dy);
111
+ setZoomX(zoomPoint.x);
112
+ setZoomY(zoomPoint.y);
113
+ setZoom(calc_zoom);
114
+ }
115
+ };
116
+ const handleMouseMove = (event) => {
117
+ if (!panZoomable)
85
118
  return;
119
+ if (status === MouseStatus.MOVE) {
120
+ if (canvasRef.current) {
121
+ const canvasEl = canvasRef.current;
122
+ const rect = canvasEl.getBoundingClientRect();
123
+ const mouseX = event.clientX - rect.left;
124
+ const mouseY = event.clientY - rect.top;
125
+ const mouse = getMousePointTransform({ x: mouseX, y: mouseY }, { x: moveX, y: moveY }, { x: zoomX, y: zoomY }, { x: dx, y: dy }, zoom || 1, canvasEl);
126
+ let x = mouse.x;
127
+ let y = mouse.y;
128
+ if (startMousePoint) {
129
+ x = x - startMousePoint.x;
130
+ y = y - startMousePoint.y;
131
+ }
132
+ setMoveX(moveX + x);
133
+ setMoveY(moveY + y);
134
+ const zoomPoint = calculatorZoomPoint(canvasEl, moveX + x, moveY + y, dx, dy);
135
+ setZoomX(zoomPoint.x);
136
+ setZoomY(zoomPoint.y);
137
+ }
138
+ }
139
+ event.preventDefault();
140
+ };
141
+ const handleMouseDown = (event) => {
142
+ if (!panZoomable)
143
+ return false;
86
144
  setStatus(MouseStatus.MOVE);
87
- const rect = canvasRef.current.getBoundingClientRect();
88
- const mouseX = e.clientX - rect.left;
89
- const mouseY = e.clientY - rect.top;
90
- const transformed = getMousePointTransform({ x: mouseX, y: mouseY }, { x: moveX, y: moveY }, { x: zoomX, y: zoomY }, { x: dx, y: dy }, zoom, canvasRef.current);
91
- setStartMousePoint({ x: transformed.x, y: transformed.y });
92
- e.preventDefault();
93
- }, [canvasRef, panZoomable, moveX, moveY, zoomX, zoomY, dx, dy, zoom]);
94
- const handleMouseMove = useCallback((e) => {
95
- if (!panZoomable || status !== MouseStatus.MOVE || !canvasRef.current)
96
- return;
97
- const rect = canvasRef.current.getBoundingClientRect();
98
- const mouseX = e.clientX - rect.left;
99
- const mouseY = e.clientY - rect.top;
100
- const transformed = getMousePointTransform({ x: mouseX, y: mouseY }, { x: moveX, y: moveY }, { x: zoomX, y: zoomY }, { x: dx, y: dy }, zoom, canvasRef.current);
101
- if (startMousePoint) {
102
- const deltaX = transformed.x - startMousePoint.x;
103
- const deltaY = transformed.y - startMousePoint.y;
104
- setMoveX((prev) => prev + deltaX);
105
- setMoveY((prev) => prev + deltaY);
145
+ if (canvasRef.current) {
146
+ const canvasEl = canvasRef.current;
147
+ const rect = canvasEl.getBoundingClientRect();
148
+ const mouseX = event.clientX - rect.left;
149
+ const mouseY = event.clientY - rect.top;
150
+ const mouse = getMousePointTransform({ x: mouseX, y: mouseY }, { x: moveX, y: moveY }, { x: zoomX, y: zoomY }, { x: dx, y: dy }, zoom || 1, canvasEl);
151
+ const x = mouse.x;
152
+ const y = mouse.y;
153
+ setStartMousePoint({
154
+ x: x,
155
+ y: y,
156
+ });
106
157
  }
107
- e.preventDefault();
108
- }, [canvasRef, panZoomable, status, moveX, moveY, zoomX, zoomY, dx, dy, zoom, startMousePoint]);
109
- const handleMouseUp = useCallback(() => {
158
+ event.preventDefault();
159
+ };
160
+ const handleMouseUp = (event) => {
110
161
  if (!panZoomable)
111
162
  return;
112
163
  setStatus(MouseStatus.STOP);
113
- }, [panZoomable]);
114
- const handleMouseLeave = useCallback(() => {
164
+ event.preventDefault();
165
+ };
166
+ const handleMouseLeave = (event) => {
115
167
  if (!panZoomable)
116
168
  return;
117
169
  setStatus(MouseStatus.STOP);
118
- }, [panZoomable]);
119
- useEffect(() => {
120
- if (!canvasRef.current || !image || (dx === 0 && dy === 0 && zoom === 1))
121
- return;
122
- drawCanvas(moveX, moveY, zoomX, zoomY, zoom, dx, dy, resolutionCanvas(canvasRef.current), image, true);
123
- }, [canvasRef, image, moveX, moveY, zoomX, zoomY, zoom, dx, dy]);
124
- const resetView = useCallback(() => {
125
- if (image)
126
- initCanvas(image);
127
- }, [image, initCanvas]);
170
+ event.preventDefault();
171
+ };
128
172
  return {
129
173
  moveX,
130
174
  moveY,
131
175
  zoom,
176
+ initZoom,
132
177
  zoomX,
133
178
  zoomY,
134
179
  dx,
@@ -140,6 +185,6 @@ export const usePanZoom = ({ canvasRef, image, panZoomable = false, resetOnImage
140
185
  handleMouseMove,
141
186
  handleMouseUp,
142
187
  handleMouseLeave,
143
- resetView,
188
+ initCanvas,
144
189
  };
145
190
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepnoid/canvas",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.cjs",