@deepnoid/canvas 0.1.35 → 0.1.37

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/README.md CHANGED
@@ -1,3 +1,6 @@
1
1
  # deepnoid-canvas
2
2
 
3
- 이 프로젝트는 본부 내 프로젝트에서 사용되는 공통 CANVAS 라이브러리입니다!!!
3
+ 이 프로젝트는 본부 내 프로젝트에서 사용되는 공통 CANVAS 라이브러리입니다!!!
4
+
5
+ AnnotationViewer → 전체 이미지 + UI 컨트롤 포함
6
+ AnnotationLayer → 실제 좌표/도형을 그리는 캔버스
@@ -0,0 +1,16 @@
1
+ import { Coordinate } from '../../types/coordinate';
2
+ type Props = {
3
+ moveX: number;
4
+ moveY: number;
5
+ zoomX: number;
6
+ zoomY: number;
7
+ zoom: number;
8
+ dx: number;
9
+ dy: number;
10
+ dw: number;
11
+ dh: number;
12
+ coordinates?: Coordinate[];
13
+ editable?: boolean;
14
+ };
15
+ declare const AnnotationLayer: ({ moveX, moveY, zoom, zoomX, zoomY, dx, dy, dw, dh, coordinates }: Props) => import("react/jsx-runtime").JSX.Element;
16
+ export default AnnotationLayer;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useRef } from 'react';
3
- import { drawCanvas, drawRect, resolutionCanvas } from '../utils/graphic';
4
- const Canvas = ({ moveX, moveY, zoom, zoomX, zoomY, dx, dy, dw, dh, coordinates }) => {
3
+ import { drawCanvas, drawRect, resolutionCanvas } from '../../utils/graphic';
4
+ const AnnotationLayer = ({ moveX, moveY, zoom, zoomX, zoomY, dx, dy, dw, dh, coordinates }) => {
5
5
  const canvas = useRef(null);
6
6
  useEffect(() => {
7
7
  const redraw = () => {
@@ -32,4 +32,4 @@ const Canvas = ({ moveX, moveY, zoom, zoomX, zoomY, dx, dy, dw, dh, coordinates
32
32
  willChange: 'transform',
33
33
  } }));
34
34
  };
35
- export default Canvas;
35
+ export default AnnotationLayer;
@@ -11,19 +11,16 @@ type CanvasState = {
11
11
  zoom: number;
12
12
  initZoom: number;
13
13
  };
14
- type UsePanZoomProps = {
14
+ type Props = {
15
15
  canvasRef: RefObject<HTMLCanvasElement | null>;
16
16
  imageRef: RefObject<HTMLImageElement>;
17
17
  panZoomable?: boolean;
18
18
  };
19
- export declare function usePanZoom({ canvasRef, imageRef, panZoomable }: UsePanZoomProps): {
19
+ export declare function useImagePanZoom({ canvasRef, imageRef, panZoomable }: Props): {
20
20
  canvasState: CanvasState;
21
- imageOnloadCount: number;
22
- setImageOnloadCount: import("react").Dispatch<import("react").SetStateAction<number>>;
23
- init: (canvas: HTMLCanvasElement, image: HTMLImageElement) => void;
24
- initCanvas: () => void;
25
- clearCanvas: () => void;
21
+ initZoomAndPosition: (canvas: HTMLCanvasElement, image: HTMLImageElement) => void;
26
22
  preserveZoomAndPosition: (canvas: HTMLCanvasElement, image: HTMLImageElement, zoomRatio: number) => void;
23
+ initCanvas: () => void;
27
24
  handleWheel: (event: WheelEvent) => void;
28
25
  handleMouseDown: (event: MouseEvent) => void;
29
26
  handleMouseMove: (event: MouseEvent) => void;
@@ -1,8 +1,7 @@
1
1
  'use client';
2
- import { useState } from 'react';
3
- import { resolutionCanvas, getMousePointTransform, canvasCenterPoint, calculatorZoomPoint, calculateInitZoom, } from '../utils/graphic';
4
- import { MouseStatus } from '../enum/common';
5
- export function usePanZoom({ canvasRef, imageRef, panZoomable = false }) {
2
+ import { useState, useRef } from 'react';
3
+ import { getMousePointTransform, canvasCenterPoint, calculatorZoomPoint, calculateInitZoom, calculateZoom, } from '../../../utils/graphic';
4
+ export function useImagePanZoom({ canvasRef, imageRef, panZoomable = false }) {
6
5
  const ZOOM_UNIT = 0.9;
7
6
  const MAX_ZOOM = 4;
8
7
  const MIN_ZOOM = 0.5;
@@ -18,16 +17,15 @@ export function usePanZoom({ canvasRef, imageRef, panZoomable = false }) {
18
17
  zoom: 0,
19
18
  initZoom: 0,
20
19
  });
21
- const [status, setStatus] = useState('');
22
- const [imageOnloadCount, setImageOnloadCount] = useState(0);
23
20
  const [startMousePoint, setStartMousePoint] = useState();
24
- const init = (canvas, image) => {
25
- const point = canvasCenterPoint(canvas, image);
26
- const zoomPoint = calculatorZoomPoint(canvas, 0, 0, point.x, point.y);
21
+ const isDraggingRef = useRef(false);
22
+ const initZoomAndPosition = (canvas, image) => {
23
+ const center = canvasCenterPoint(canvas, image);
24
+ const zoomPoint = calculatorZoomPoint(canvas, 0, 0, center.x, center.y);
27
25
  const initZoomValue = calculateInitZoom(canvas, image);
28
26
  setCanvasState({
29
- dx: point.x,
30
- dy: point.y,
27
+ dx: center.x,
28
+ dy: center.y,
31
29
  dw: image.width,
32
30
  dh: image.height,
33
31
  moveX: 0,
@@ -40,12 +38,12 @@ export function usePanZoom({ canvasRef, imageRef, panZoomable = false }) {
40
38
  };
41
39
  const preserveZoomAndPosition = (canvas, image, zoomRatio) => {
42
40
  const prevZoomRatio = zoomRatio || 1;
43
- const point = canvasCenterPoint(canvas, image);
41
+ const center = canvasCenterPoint(canvas, image);
44
42
  const newInitZoom = calculateInitZoom(canvas, image);
45
- const zoomPoint = calculatorZoomPoint(canvas, canvasState.moveX, canvasState.moveY, point.x, point.y);
43
+ const zoomPoint = calculatorZoomPoint(canvas, canvasState.moveX, canvasState.moveY, center.x, center.y);
46
44
  setCanvasState({
47
- dx: point.x,
48
- dy: point.y,
45
+ dx: center.x,
46
+ dy: center.y,
49
47
  dw: image.width,
50
48
  dh: image.height,
51
49
  moveX: canvasState.moveX,
@@ -59,39 +57,22 @@ export function usePanZoom({ canvasRef, imageRef, panZoomable = false }) {
59
57
  const initCanvas = () => {
60
58
  if (!canvasRef.current)
61
59
  return;
62
- const canvas = resolutionCanvas(canvasRef.current);
63
- if (!canvas)
64
- return;
65
- init(canvas, imageRef.current);
66
- setImageOnloadCount((prev) => prev + 1);
67
- };
68
- const clearCanvas = () => {
69
- if (!canvasRef.current)
70
- return;
71
- const canvas = resolutionCanvas(canvasRef.current);
72
- if (!canvas)
73
- return;
74
- const ctx = canvas.getContext('2d');
75
- ctx?.clearRect(0, 0, canvas.width, canvas.height);
60
+ initZoomAndPosition(canvasRef.current, imageRef.current);
76
61
  };
77
62
  const handleWheel = (event) => {
78
63
  if (!panZoomable || !canvasRef.current || canvasState.initZoom <= 0)
79
64
  return;
80
- let calcZoom = event.deltaY < 0 ? canvasState.zoom * (1 / ZOOM_UNIT) : canvasState.zoom * ZOOM_UNIT;
81
- if (canvasState.initZoom * MAX_ZOOM < canvasState.zoom * ZOOM_UNIT)
82
- calcZoom = calcZoom * ZOOM_UNIT;
83
- if (canvasState.initZoom * MIN_ZOOM > canvasState.zoom * ZOOM_UNIT)
84
- calcZoom = calcZoom * (1 / ZOOM_UNIT);
65
+ const newZoom = calculateZoom(canvasState.zoom, event.deltaY, canvasState.initZoom * MIN_ZOOM, canvasState.initZoom * MAX_ZOOM, ZOOM_UNIT);
85
66
  const zoomPoint = calculatorZoomPoint(canvasRef.current, canvasState.moveX, canvasState.moveY, canvasState.dx, canvasState.dy);
86
67
  setCanvasState({
87
68
  ...canvasState,
88
69
  zoomX: zoomPoint.x,
89
70
  zoomY: zoomPoint.y,
90
- zoom: calcZoom,
71
+ zoom: newZoom,
91
72
  });
92
73
  };
93
74
  const handleMouseMove = (event) => {
94
- if (!panZoomable || !canvasRef.current || status !== MouseStatus.MOVE)
75
+ if (!panZoomable || !canvasRef.current || !isDraggingRef.current)
95
76
  return;
96
77
  const canvas = canvasRef.current;
97
78
  const rect = canvas.getBoundingClientRect();
@@ -117,7 +98,7 @@ export function usePanZoom({ canvasRef, imageRef, panZoomable = false }) {
117
98
  const handleMouseDown = (event) => {
118
99
  if (!panZoomable || !canvasRef.current)
119
100
  return;
120
- setStatus(MouseStatus.MOVE);
101
+ isDraggingRef.current = true;
121
102
  const canvas = canvasRef.current;
122
103
  const rect = canvas.getBoundingClientRect();
123
104
  const mouseX = event.clientX - rect.left;
@@ -129,23 +110,20 @@ export function usePanZoom({ canvasRef, imageRef, panZoomable = false }) {
129
110
  const handleMouseUp = (event) => {
130
111
  if (!panZoomable || !canvasRef.current)
131
112
  return;
132
- setStatus(MouseStatus.STOP);
113
+ isDraggingRef.current = false;
133
114
  event.preventDefault();
134
115
  };
135
116
  const handleMouseLeave = (event) => {
136
117
  if (!panZoomable || !canvasRef.current)
137
118
  return;
138
- setStatus(MouseStatus.STOP);
119
+ isDraggingRef.current = false;
139
120
  event.preventDefault();
140
121
  };
141
122
  return {
142
123
  canvasState,
143
- imageOnloadCount,
144
- setImageOnloadCount,
145
- init,
146
- initCanvas,
147
- clearCanvas,
124
+ initZoomAndPosition,
148
125
  preserveZoomAndPosition,
126
+ initCanvas,
149
127
  handleWheel,
150
128
  handleMouseDown,
151
129
  handleMouseMove,
@@ -1,5 +1,5 @@
1
1
  import { ComponentType, ReactNode } from 'react';
2
- import { Coordinate } from '../types/coordinate';
2
+ import { Coordinate } from '../../types/coordinate';
3
3
  export type ZoomButtonType = {
4
4
  onClick: () => void;
5
5
  children: ReactNode;
@@ -18,5 +18,5 @@ type Props = {
18
18
  onImageLoadSuccess?: () => void;
19
19
  onImageLoadError?: (error: Error) => void;
20
20
  };
21
- declare const AnnotatedCanvas: ({ image, coordinates, panZoomable, ZoomButton, resetOnImageChange, editable, timeout, onImageLoadSuccess, onImageLoadError, }: Props) => import("react/jsx-runtime").JSX.Element;
22
- export default AnnotatedCanvas;
21
+ declare const AnnotationViewer: ({ image, coordinates, panZoomable, ZoomButton, resetOnImageChange, editable, timeout, onImageLoadSuccess, onImageLoadError, }: Props) => import("react/jsx-runtime").JSX.Element;
22
+ export default AnnotationViewer;
@@ -1,25 +1,24 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, useState } from 'react';
4
- import Canvas from './Canvas';
5
- import { usePanZoom } from '../hooks/usePanZoom';
6
- import useResizeObserver from '../hooks/useResizeObserver';
7
- import { drawCanvas, resolutionCanvas } from '../utils/graphic';
8
- import { useDebounce } from '../hooks/useDebounce';
9
- const AnnotatedCanvas = ({ image, coordinates, panZoomable = false, ZoomButton, resetOnImageChange = true, editable = false, timeout = 10000, onImageLoadSuccess, onImageLoadError, }) => {
4
+ import AnnotationLayer from '../AnnotationLayer';
5
+ import { useImagePanZoom } from './_hooks/useImagePanZoom';
6
+ import useResizeObserver from '../../hooks/useResizeObserver';
7
+ import { clearCanvasRect, drawCanvas, resolutionCanvas } from '../../utils/graphic';
8
+ import { useDebounce } from '../../hooks/useDebounce';
9
+ const AnnotationViewer = ({ image, coordinates, panZoomable = false, ZoomButton, resetOnImageChange = true, editable = false, timeout = 10000, onImageLoadSuccess, onImageLoadError, }) => {
10
10
  const canvasRef = useRef(null);
11
11
  const imageRef = useRef(new Image());
12
+ const [displayCoordinates, setDisplayCoordinates] = useState();
12
13
  const debouncedHandleResize = useDebounce((size) => {
13
14
  if (image && size.width && size.height && canvasRef.current && imageRef.current.src) {
14
- console.log('> Debounced resize - canvasRef.current:', canvasRef.current);
15
15
  const canvas = resolutionCanvas(canvasRef.current);
16
16
  if (canvas)
17
- init(canvas, imageRef.current);
17
+ initZoomAndPosition(canvas, imageRef.current);
18
18
  }
19
19
  }, 150);
20
20
  useResizeObserver({ ref: canvasRef, onResize: debouncedHandleResize });
21
- const [displayCoordinates, setDisplayCoordinates] = useState();
22
- const { canvasState, init, initCanvas, clearCanvas, preserveZoomAndPosition, handleWheel, handleMouseDown, handleMouseMove, handleMouseUp, handleMouseLeave, imageOnloadCount, setImageOnloadCount, } = usePanZoom({ canvasRef, imageRef, panZoomable });
21
+ const { canvasState, initZoomAndPosition, initCanvas, preserveZoomAndPosition, handleWheel, handleMouseDown, handleMouseMove, handleMouseUp, handleMouseLeave, } = useImagePanZoom({ canvasRef, imageRef, panZoomable });
23
22
  useEffect(() => {
24
23
  const createEmptyImage = () => {
25
24
  const img = new Image();
@@ -27,7 +26,7 @@ const AnnotatedCanvas = ({ image, coordinates, panZoomable = false, ZoomButton,
27
26
  return img;
28
27
  };
29
28
  const resetCanvas = (errorMsg) => {
30
- clearCanvas();
29
+ clearCanvasRect(canvasRef.current);
31
30
  setDisplayCoordinates([]);
32
31
  imageRef.current = createEmptyImage();
33
32
  if (errorMsg)
@@ -42,15 +41,12 @@ const AnnotatedCanvas = ({ image, coordinates, panZoomable = false, ZoomButton,
42
41
  return;
43
42
  onImageLoadSuccess?.();
44
43
  if (resetOnImageChange) {
45
- const canvas = resolutionCanvas(canvasRef.current);
46
- if (canvas)
47
- init(canvas, tempImage);
44
+ setTimeout(() => initCanvas(), 0);
48
45
  }
49
46
  else {
50
47
  preserveZoomAndPosition(canvasRef.current, tempImage, canvasState.zoom / (canvasState.initZoom || 1));
51
48
  }
52
49
  imageRef.current = tempImage;
53
- setImageOnloadCount((c) => c + 1);
54
50
  setDisplayCoordinates(coordinates);
55
51
  };
56
52
  tempImage.onerror = () => !cancelled && resetCanvas(`Failed to load image: ${image}`);
@@ -68,7 +64,7 @@ const AnnotatedCanvas = ({ image, coordinates, panZoomable = false, ZoomButton,
68
64
  const canvas = resolutionCanvas(canvasRef.current);
69
65
  if (canvas)
70
66
  redraw(canvas);
71
- }, [canvasState, imageOnloadCount]);
67
+ }, [canvasState]);
72
68
  return (_jsx("div", { style: { width: '100%', height: '100%', display: 'flex', flex: 1 }, children: _jsxs("div", { onWheel: handleWheel, onMouseMove: handleMouseMove, onMouseDown: handleMouseDown, onMouseUp: handleMouseUp, onMouseLeave: handleMouseLeave, style: {
73
69
  flex: 1,
74
70
  position: 'relative',
@@ -79,6 +75,6 @@ const AnnotatedCanvas = ({ image, coordinates, panZoomable = false, ZoomButton,
79
75
  height: '100%',
80
76
  left: 0,
81
77
  top: 0,
82
- } }), _jsx(Canvas, { ...canvasState, coordinates: displayCoordinates, editable: editable }), ZoomButton && canvasState.initZoom > 0 && (_jsx(ZoomButton, { onClick: initCanvas, children: `${Math.round((canvasState.zoom / canvasState.initZoom) * 100)}%` }))] }) }));
78
+ } }), _jsx(AnnotationLayer, { ...canvasState, coordinates: displayCoordinates, editable: editable }), ZoomButton && canvasState.initZoom > 0 && (_jsx(ZoomButton, { onClick: initCanvas, children: `${Math.round((canvasState.zoom / canvasState.initZoom) * 100)}%` }))] }) }));
83
79
  };
84
- export default AnnotatedCanvas;
80
+ export default AnnotationViewer;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import AnnotatedCanvas from './components/AnnotatedCanvas';
2
- export { AnnotatedCanvas };
3
- export type { ZoomButtonType } from './components/AnnotatedCanvas';
1
+ import AnnotationViewer from './components/AnnotationViewer';
2
+ export { AnnotationViewer };
3
+ export type { ZoomButtonType } from './components/AnnotationViewer';
4
4
  export type { Rectangle, Coordinate } from './types/coordinate';
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import AnnotatedCanvas from './components/AnnotatedCanvas';
2
- export { AnnotatedCanvas };
1
+ import AnnotationViewer from './components/AnnotationViewer';
2
+ export { AnnotationViewer };
@@ -34,4 +34,15 @@ export declare const __rotate: (mouse: Point, rotate: number, center: Point) =>
34
34
  * @returns 캔버스 크기 해상도에 맞춰 변형된 마우스 좌표
35
35
  */
36
36
  export declare const getMousePointTransform: (mousePoint: Point, movePoint: Point, zoomPoint: Point, originPoint: Point, zoom: number, canvas_el: HTMLCanvasElement, rotate?: number) => Point;
37
+ export declare const clearCanvasRect: (canvas: HTMLCanvasElement | null | undefined) => void;
38
+ /**
39
+ * 마우스 휠 기반 확대/축소 계산
40
+ * @param currentZoom 현재 zoom 값
41
+ * @param deltaY wheelEvent.deltaY
42
+ * @param minZoom 최소 zoom
43
+ * @param maxZoom 최대 zoom
44
+ * @param zoomUnit 확대/축소 단위
45
+ * @returns 계산된 zoom 값
46
+ */
47
+ export declare const calculateZoom: (currentZoom: number, deltaY: number, minZoom: number, maxZoom: number, zoomUnit: number) => number;
37
48
  export {};
@@ -60,17 +60,13 @@ export const setRectangleStyle = (context, coordinate) => {
60
60
  context.strokeStyle = 'rgba(36, 162, 91, 1)';
61
61
  }
62
62
  };
63
- // [캔버스] 해상도
64
63
  export const resolutionCanvas = (canvas, width, height) => {
65
- if (canvas) {
66
- const canvas_el = canvas;
67
- canvas_el.width = width ? width : canvas_el.clientWidth;
68
- canvas_el.height = height ? height : canvas_el.clientHeight;
69
- return canvas_el;
70
- }
71
- else {
64
+ if (!canvas)
72
65
  return undefined;
73
- }
66
+ const newCanvas = canvas;
67
+ newCanvas.width = width || newCanvas.clientWidth;
68
+ newCanvas.height = height || newCanvas.clientHeight;
69
+ return newCanvas;
74
70
  };
75
71
  // [좌표 변형] 원점
76
72
  export const __origin = (mouse, origin) => {
@@ -135,3 +131,28 @@ export const getMousePointTransform = (mousePoint, movePoint, zoomPoint, originP
135
131
  mouse = __zoom(mouse, zoomPoint, zoom, canvas_el);
136
132
  return mouse;
137
133
  };
134
+ export const clearCanvasRect = (canvas) => {
135
+ if (!canvas)
136
+ return;
137
+ const ctx = canvas.getContext('2d');
138
+ const resolvedCanvas = resolutionCanvas(canvas);
139
+ if (resolvedCanvas)
140
+ ctx?.clearRect(0, 0, resolvedCanvas.width, resolvedCanvas.height);
141
+ };
142
+ /**
143
+ * 마우스 휠 기반 확대/축소 계산
144
+ * @param currentZoom 현재 zoom 값
145
+ * @param deltaY wheelEvent.deltaY
146
+ * @param minZoom 최소 zoom
147
+ * @param maxZoom 최대 zoom
148
+ * @param zoomUnit 확대/축소 단위
149
+ * @returns 계산된 zoom 값
150
+ */
151
+ export const calculateZoom = (currentZoom, deltaY, minZoom, maxZoom, zoomUnit) => {
152
+ let newZoom = deltaY < 0 ? currentZoom * (1 / zoomUnit) : currentZoom * zoomUnit;
153
+ if (newZoom > maxZoom)
154
+ newZoom = maxZoom;
155
+ if (newZoom < minZoom)
156
+ newZoom = minZoom;
157
+ return newZoom;
158
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepnoid/canvas",
3
- "version": "0.1.35",
3
+ "version": "0.1.37",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.cjs",
@@ -1,16 +0,0 @@
1
- import { Coordinate } from '../types/coordinate';
2
- type Props = {
3
- moveX: number;
4
- moveY: number;
5
- zoomX: number;
6
- zoomY: number;
7
- zoom: number;
8
- dx: number;
9
- dy: number;
10
- dw: number;
11
- dh: number;
12
- coordinates?: Coordinate[];
13
- editable?: boolean;
14
- };
15
- declare const Canvas: ({ moveX, moveY, zoom, zoomX, zoomY, dx, dy, dw, dh, coordinates }: Props) => import("react/jsx-runtime").JSX.Element;
16
- export default Canvas;
@@ -1,4 +0,0 @@
1
- export declare enum MouseStatus {
2
- MOVE = "MOVE",
3
- STOP = "STOP"
4
- }
@@ -1,5 +0,0 @@
1
- export var MouseStatus;
2
- (function (MouseStatus) {
3
- MouseStatus["MOVE"] = "MOVE";
4
- MouseStatus["STOP"] = "STOP";
5
- })(MouseStatus || (MouseStatus = {}));