@deepnoid/canvas 0.1.71 → 0.1.72
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.
- package/dist/react/AnnotationCanvas.js +68 -60
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { AnnotationEngine } from '../engine/public/annotationEngine';
|
|
5
5
|
import useResizeObserver from './hooks/useResizeObserver';
|
|
@@ -8,25 +8,18 @@ import { useHotkeys } from './hooks/useHotkeys';
|
|
|
8
8
|
const AnnotationCanvas = ({ image, annotations = [], setAnnotations, options, drawing, events, enableHotkeys = true, editable = true, }) => {
|
|
9
9
|
const { panZoomEnabled, zoom, ZoomButton, resetOnImageChange = false } = options || {};
|
|
10
10
|
const { onImageLoadSuccess, onImageLoadError } = events || {};
|
|
11
|
-
const
|
|
12
|
-
const annotationsCanvasRef = useRef(null);
|
|
11
|
+
const containerRef = useRef(null);
|
|
13
12
|
const engineRef = useRef(null);
|
|
14
13
|
const pendingAnnotationsRef = useRef(null);
|
|
15
14
|
const imageLoadingRef = useRef(false);
|
|
16
15
|
const currentImageRef = useRef(image);
|
|
17
16
|
const engineIdRef = useRef(0);
|
|
18
17
|
const [, forceRender] = useState(0);
|
|
19
|
-
/* ---------- Resize ---------- */
|
|
18
|
+
/* ---------- Resize Observer ---------- */
|
|
20
19
|
useResizeObserver({
|
|
21
|
-
ref:
|
|
22
|
-
onResize: useDebounce((
|
|
23
|
-
|
|
24
|
-
const canvas = engine?.getImageCanvas();
|
|
25
|
-
if (!engine || !canvas)
|
|
26
|
-
return;
|
|
27
|
-
if (canvas.width !== size.width || canvas.height !== size.height) {
|
|
28
|
-
engine.initImageCanvas(true);
|
|
29
|
-
}
|
|
20
|
+
ref: containerRef,
|
|
21
|
+
onResize: useDebounce(() => {
|
|
22
|
+
engineRef.current?.initImageCanvas(true);
|
|
30
23
|
}, 150),
|
|
31
24
|
});
|
|
32
25
|
/* ---------- Hotkeys ---------- */
|
|
@@ -38,28 +31,44 @@ const AnnotationCanvas = ({ image, annotations = [], setAnnotations, options, dr
|
|
|
38
31
|
});
|
|
39
32
|
/* ---------- Image / Engine lifecycle ---------- */
|
|
40
33
|
useEffect(() => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
engineRef.current?.destroy();
|
|
45
|
-
engineRef.current = null;
|
|
46
|
-
pendingAnnotationsRef.current = null;
|
|
47
|
-
currentImageRef.current = image;
|
|
48
|
-
engineIdRef.current++;
|
|
49
|
-
setAnnotations?.([]);
|
|
50
|
-
}
|
|
34
|
+
if (!containerRef.current)
|
|
35
|
+
return;
|
|
36
|
+
const prevEngine = engineRef.current;
|
|
51
37
|
imageLoadingRef.current = true;
|
|
52
38
|
if (!image?.trim()) {
|
|
53
|
-
|
|
39
|
+
if (prevEngine) {
|
|
40
|
+
prevEngine.destroy();
|
|
41
|
+
containerRef.current.removeChild(prevEngine.getImageCanvas());
|
|
42
|
+
containerRef.current.removeChild(prevEngine.getAnnotationsCanvas());
|
|
43
|
+
}
|
|
54
44
|
engineRef.current = null;
|
|
45
|
+
pendingAnnotationsRef.current = null;
|
|
55
46
|
imageLoadingRef.current = false;
|
|
47
|
+
currentImageRef.current = '';
|
|
56
48
|
return;
|
|
57
49
|
}
|
|
50
|
+
const newEngineId = engineIdRef.current + 1;
|
|
51
|
+
engineIdRef.current = newEngineId;
|
|
58
52
|
let cancelled = false;
|
|
59
53
|
const img = new Image();
|
|
60
54
|
img.onload = () => {
|
|
61
|
-
if (cancelled || !
|
|
55
|
+
if (cancelled || !containerRef.current)
|
|
62
56
|
return;
|
|
57
|
+
const imageCanvas = document.createElement('canvas');
|
|
58
|
+
const annotationsCanvas = document.createElement('canvas');
|
|
59
|
+
imageCanvas.style.position = 'absolute';
|
|
60
|
+
imageCanvas.style.width = '100%';
|
|
61
|
+
imageCanvas.style.height = '100%';
|
|
62
|
+
annotationsCanvas.style.position = 'absolute';
|
|
63
|
+
annotationsCanvas.style.width = '100%';
|
|
64
|
+
annotationsCanvas.style.height = '100%';
|
|
65
|
+
const containerRect = containerRef.current.getBoundingClientRect();
|
|
66
|
+
const dpr = window.devicePixelRatio || 1;
|
|
67
|
+
imageCanvas.width = containerRect.width * dpr;
|
|
68
|
+
imageCanvas.height = containerRect.height * dpr;
|
|
69
|
+
annotationsCanvas.width = containerRect.width * dpr;
|
|
70
|
+
annotationsCanvas.height = containerRect.height * dpr;
|
|
71
|
+
const prevImageCanvasState = prevEngine?.getImageCanvasState();
|
|
63
72
|
const imageCanvasState = !resetOnImageChange && prevImageCanvasState
|
|
64
73
|
? prevImageCanvasState
|
|
65
74
|
: {
|
|
@@ -74,52 +83,61 @@ const AnnotationCanvas = ({ image, annotations = [], setAnnotations, options, dr
|
|
|
74
83
|
dh: img.height,
|
|
75
84
|
initZoom: 1,
|
|
76
85
|
};
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
engineRef.current = new AnnotationEngine({
|
|
81
|
-
imageCanvas: imageCanvasRef.current,
|
|
86
|
+
const engine = new AnnotationEngine({
|
|
87
|
+
imageCanvas,
|
|
88
|
+
annotationsCanvas,
|
|
82
89
|
image: img,
|
|
83
90
|
imageCanvasState,
|
|
84
|
-
|
|
85
|
-
annotations: initialAnnotations,
|
|
91
|
+
annotations: pendingAnnotationsRef.current ?? [],
|
|
86
92
|
setAnnotations: (next) => {
|
|
87
|
-
if (engineIdRef.current ===
|
|
93
|
+
if (engineIdRef.current === newEngineId)
|
|
88
94
|
setAnnotations?.(next);
|
|
89
|
-
}
|
|
90
95
|
},
|
|
91
96
|
drawing,
|
|
92
97
|
editable,
|
|
98
|
+
enableHotkeys,
|
|
93
99
|
panZoomEnabled,
|
|
94
100
|
zoom,
|
|
95
|
-
enableHotkeys,
|
|
96
101
|
onChange: () => forceRender((v) => v + 1),
|
|
97
102
|
});
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
103
|
+
requestAnimationFrame(() => {
|
|
104
|
+
containerRef.current.appendChild(imageCanvas);
|
|
105
|
+
containerRef.current.appendChild(annotationsCanvas);
|
|
106
|
+
engine.initImageCanvas(resetOnImageChange);
|
|
107
|
+
if (prevEngine) {
|
|
108
|
+
prevEngine.destroy();
|
|
109
|
+
containerRef.current.removeChild(prevEngine.getImageCanvas());
|
|
110
|
+
containerRef.current.removeChild(prevEngine.getAnnotationsCanvas());
|
|
111
|
+
}
|
|
112
|
+
engineRef.current = engine;
|
|
113
|
+
pendingAnnotationsRef.current = null;
|
|
114
|
+
imageLoadingRef.current = false;
|
|
115
|
+
currentImageRef.current = image;
|
|
116
|
+
onImageLoadSuccess?.();
|
|
117
|
+
});
|
|
101
118
|
};
|
|
102
119
|
img.onerror = () => {
|
|
103
|
-
if (
|
|
120
|
+
if (!containerRef.current)
|
|
104
121
|
return;
|
|
105
|
-
|
|
106
|
-
|
|
122
|
+
if (prevEngine) {
|
|
123
|
+
prevEngine.destroy();
|
|
124
|
+
containerRef.current.removeChild(prevEngine.getImageCanvas());
|
|
125
|
+
containerRef.current.removeChild(prevEngine.getAnnotationsCanvas());
|
|
126
|
+
}
|
|
107
127
|
engineRef.current = null;
|
|
128
|
+
pendingAnnotationsRef.current = null;
|
|
108
129
|
imageLoadingRef.current = false;
|
|
130
|
+
currentImageRef.current = '';
|
|
109
131
|
onImageLoadError?.(new Error(`Failed to load image: ${image}`));
|
|
110
132
|
};
|
|
111
133
|
img.src = image;
|
|
112
134
|
return () => {
|
|
113
135
|
cancelled = true;
|
|
114
|
-
img.onload = null;
|
|
115
|
-
img.onerror = null;
|
|
116
136
|
};
|
|
117
137
|
}, [image, resetOnImageChange]);
|
|
118
138
|
/* ---------- External annotations ---------- */
|
|
119
139
|
useEffect(() => {
|
|
120
|
-
if (!annotations)
|
|
121
|
-
return;
|
|
122
|
-
if (imageLoadingRef.current || !engineRef.current) {
|
|
140
|
+
if (!annotations || imageLoadingRef.current || !engineRef.current) {
|
|
123
141
|
pendingAnnotationsRef.current = annotations;
|
|
124
142
|
return;
|
|
125
143
|
}
|
|
@@ -130,20 +148,10 @@ const AnnotationCanvas = ({ image, annotations = [], setAnnotations, options, dr
|
|
|
130
148
|
}
|
|
131
149
|
}, [annotations]);
|
|
132
150
|
/* ---------- Options sync ---------- */
|
|
133
|
-
useEffect(() =>
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
useEffect(() => {
|
|
137
|
-
engineRef.current?.setEditable(editable);
|
|
138
|
-
}, [editable]);
|
|
139
|
-
useEffect(() => {
|
|
140
|
-
engineRef.current?.setDrawing(drawing);
|
|
141
|
-
}, [drawing]);
|
|
151
|
+
useEffect(() => engineRef.current?.setPanZoomEnabled(!!panZoomEnabled), [panZoomEnabled]);
|
|
152
|
+
useEffect(() => engineRef.current?.setEditable(editable), [editable]);
|
|
153
|
+
useEffect(() => engineRef.current?.setDrawing(drawing), [drawing]);
|
|
142
154
|
/* ---------- Render ---------- */
|
|
143
|
-
return (_jsx("div", { style: { width: '100%', height: '100%', display: 'flex', flex: 1 },
|
|
144
|
-
flex: 1,
|
|
145
|
-
position: 'relative',
|
|
146
|
-
cursor: !panZoomEnabled || editable ? 'default' : 'grab',
|
|
147
|
-
}, children: [_jsx("canvas", { ref: imageCanvasRef, style: { position: 'absolute', width: '100%', height: '100%' } }), _jsx("canvas", { ref: annotationsCanvasRef, style: { position: 'absolute', width: '100%', height: '100%' } }), ZoomButton && (_jsx(ZoomButton, { onClick: () => engineRef.current?.initImageCanvas(true), children: engineRef.current?.getZoomRatioLabel() }))] }) }));
|
|
155
|
+
return (_jsx("div", { ref: containerRef, style: { width: '100%', height: '100%', position: 'relative', display: 'flex', flex: 1 }, onWheel: (e) => engineRef.current?.onWheel(e.nativeEvent), onMouseDown: (e) => engineRef.current?.onMouseDown(e.nativeEvent), onMouseMove: (e) => engineRef.current?.onMouseMove(e.nativeEvent), onMouseUp: (e) => engineRef.current?.onMouseUp(e.nativeEvent), onMouseLeave: (e) => engineRef.current?.onMouseLeave(e.nativeEvent), onContextMenu: (e) => e.preventDefault(), children: ZoomButton && (_jsx(ZoomButton, { onClick: () => engineRef.current?.initImageCanvas(true), children: engineRef.current?.getZoomRatioLabel() })) }));
|
|
148
156
|
};
|
|
149
157
|
export default AnnotationCanvas;
|