@deepnoid/canvas 0.1.29 → 0.1.31

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.
@@ -14,8 +14,9 @@ type Props = {
14
14
  }>;
15
15
  resetOnImageChange?: boolean;
16
16
  editable?: boolean;
17
- onImageLoadError?: (error: Error) => void;
17
+ timeout?: number;
18
18
  onImageLoadSuccess?: () => void;
19
+ onImageLoadError?: (error: Error) => void;
19
20
  };
20
- declare const AnnotatedCanvas: ({ image, coordinates, panZoomable, ZoomButton, resetOnImageChange, editable, onImageLoadError, onImageLoadSuccess, }: Props) => import("react/jsx-runtime").JSX.Element | null;
21
+ declare const AnnotatedCanvas: ({ image, coordinates, panZoomable, ZoomButton, resetOnImageChange, editable, timeout, onImageLoadSuccess, onImageLoadError, }: Props) => import("react/jsx-runtime").JSX.Element;
21
22
  export default AnnotatedCanvas;
@@ -1,32 +1,80 @@
1
1
  'use client';
2
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, useState } from 'react';
4
4
  import Canvas from './Canvas';
5
- import { useImageLoader } from '../hooks/useImageLoader';
6
5
  import { usePanZoom } from '../hooks/usePanZoom';
7
- const AnnotatedCanvas = ({ image, coordinates, panZoomable = false, ZoomButton, resetOnImageChange = true, editable = false, onImageLoadError, onImageLoadSuccess, }) => {
6
+ import useResizeObserver from '../hooks/useResizeObserver';
7
+ import { drawCanvas, resolutionCanvas } from '../utils/graphic';
8
+ const AnnotatedCanvas = ({ image, coordinates, panZoomable = false, ZoomButton, resetOnImageChange = true, editable = false, timeout = 10000, onImageLoadSuccess, onImageLoadError, }) => {
8
9
  const canvasRef = useRef(null);
9
10
  const imageRef = useRef(new Image());
11
+ const { width, height } = useResizeObserver({ ref: canvasRef });
10
12
  const [displayCoordinates, setDisplayCoordinates] = useState();
11
- const { image: loadedImage, isLoading, error, } = useImageLoader(image, imageRef, 10000, onImageLoadSuccess, onImageLoadError);
12
- const { moveX, moveY, zoom, initZoom, zoomX, zoomY, dx, dy, dw, dh, handleWheel, handleMouseDown, handleMouseMove, handleMouseUp, handleMouseLeave, initCanvas, } = usePanZoom({
13
- canvasRef,
14
- imageRef,
15
- panZoomable,
16
- resetOnImageChange,
17
- });
18
- const loaded = loadedImage && !isLoading;
13
+ const { canvasState, init, initCanvas, clearCanvas, preserveZoomAndPosition, handleWheel, handleMouseDown, handleMouseMove, handleMouseUp, handleMouseLeave, imageOnloadCount, setImageOnloadCount, } = usePanZoom({ canvasRef, imageRef, panZoomable });
19
14
  useEffect(() => {
20
- if (loaded) {
15
+ if (!image || image.trim() === '') {
16
+ clearCanvas();
17
+ return;
18
+ }
19
+ imageRef.current.src = image;
20
+ imageRef.current.onload = () => {
21
+ onImageLoadSuccess?.();
22
+ const canvas = resolutionCanvas(canvasRef.current);
23
+ if (!canvas)
24
+ return;
25
+ if (resetOnImageChange) {
26
+ init(canvas, imageRef.current);
27
+ }
28
+ else {
29
+ preserveZoomAndPosition(canvas, imageRef.current, canvasState.zoom / canvasState.initZoom);
30
+ }
31
+ setImageOnloadCount((prev) => prev + 1);
21
32
  setDisplayCoordinates(coordinates);
33
+ };
34
+ imageRef.current.onerror = () => {
35
+ const err = new Error(`Failed to load image: ${image}`);
36
+ onImageLoadError?.(err);
37
+ clearCanvas();
38
+ setDisplayCoordinates([]);
39
+ };
40
+ }, [image, resetOnImageChange]);
41
+ useEffect(() => {
42
+ if (image && width && height) {
43
+ const canvas = resolutionCanvas(canvasRef.current);
44
+ if (canvas)
45
+ init(canvas, imageRef.current);
22
46
  }
23
- }, [loaded, coordinates]);
24
- if (!image || error)
25
- return null;
26
- 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: {
47
+ }, [width, height]);
48
+ useEffect(() => {
49
+ if (!canvasRef.current)
50
+ return;
51
+ const redraw = (canvas) => {
52
+ drawCanvas(canvasState.moveX, canvasState.moveY, canvasState.zoomX, canvasState.zoomY, canvasState.zoom, canvasState.dx, canvasState.dy, canvas, imageRef.current, true);
53
+ };
54
+ const canvas = resolutionCanvas(canvasRef.current);
55
+ if (canvas)
56
+ redraw(canvas);
57
+ }, [canvasState, imageOnloadCount]);
58
+ 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: {
27
59
  flex: 1,
28
60
  position: 'relative',
29
61
  cursor: panZoomable ? 'grab' : 'default',
30
- }, 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 && zoom > 0 && (_jsx(ZoomButton, { onClick: initCanvas, children: `${Math.round((zoom / initZoom) * 100)}%` }))] })) }) }));
62
+ }, children: [_jsx("canvas", { ref: canvasRef, style: {
63
+ position: 'absolute',
64
+ width: '100%',
65
+ height: '100%',
66
+ left: 0,
67
+ top: 0,
68
+ } }), _jsx(Canvas, { ...canvasState,
69
+ // moveX={canvasState.moveX}
70
+ // moveY={canvasState.moveY}
71
+ // zoomX={canvasState.zoomX}
72
+ // zoomY={canvasState.zoomY}
73
+ // zoom={canvasState.zoom}
74
+ // dx={canvasState.dx}
75
+ // dy={canvasState.dy}
76
+ // dw={canvasState.dw}
77
+ // dh={canvasState.dh}
78
+ coordinates: displayCoordinates, editable: editable }), ZoomButton && canvasState.initZoom > 0 && (_jsx(ZoomButton, { onClick: initCanvas, children: `${Math.round((canvasState.zoom / canvasState.initZoom) * 100)}%` }))] }) }));
31
79
  };
32
80
  export default AnnotatedCanvas;
@@ -1,26 +1,33 @@
1
1
  import { RefObject, WheelEvent, MouseEvent } from 'react';
2
- type UsePanZoomProps = {
3
- canvasRef: RefObject<HTMLCanvasElement | null>;
4
- imageRef: RefObject<HTMLImageElement | null>;
5
- panZoomable?: boolean;
6
- resetOnImageChange?: boolean;
7
- };
8
- export declare const usePanZoom: ({ canvasRef, imageRef, panZoomable, resetOnImageChange, }: UsePanZoomProps) => {
9
- moveX: number;
10
- moveY: number;
11
- zoom: number;
12
- initZoom: number;
13
- zoomX: number;
14
- zoomY: number;
2
+ type CanvasState = {
15
3
  dx: number;
16
4
  dy: number;
17
5
  dw: number;
18
6
  dh: number;
7
+ moveX: number;
8
+ moveY: number;
9
+ zoomX: number;
10
+ zoomY: number;
11
+ zoom: number;
12
+ initZoom: number;
13
+ };
14
+ type UsePanZoomProps = {
15
+ canvasRef: RefObject<HTMLCanvasElement | null>;
16
+ imageRef: RefObject<HTMLImageElement>;
17
+ panZoomable?: boolean;
18
+ };
19
+ export declare function usePanZoom({ canvasRef, imageRef, panZoomable }: UsePanZoomProps): {
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;
26
+ preserveZoomAndPosition: (canvas: HTMLCanvasElement, image: HTMLImageElement, zoomRatio: number) => void;
19
27
  handleWheel: (event: WheelEvent) => void;
20
28
  handleMouseDown: (event: MouseEvent) => void;
21
29
  handleMouseMove: (event: MouseEvent) => void;
22
30
  handleMouseUp: (event: MouseEvent) => void;
23
31
  handleMouseLeave: (event: MouseEvent) => void;
24
- initCanvas: () => void;
25
32
  };
26
33
  export {};
@@ -1,169 +1,155 @@
1
1
  'use client';
2
- import { useCallback, useEffect, useState } from 'react';
3
- import { resolutionCanvas, drawCanvas, getMousePointTransform, canvasCenterPoint } from '../utils/graphic';
2
+ import { useState } from 'react';
3
+ import { resolutionCanvas, getMousePointTransform, canvasCenterPoint, calculatorZoomPoint, calculateInitZoom, } from '../utils/graphic';
4
4
  import { MouseStatus } from '../enum/common';
5
- import useResizeObserver from './useResizeObserver';
6
- export const usePanZoom = ({ canvasRef, imageRef, panZoomable = false, resetOnImageChange = true, }) => {
5
+ export function usePanZoom({ canvasRef, imageRef, panZoomable = false }) {
7
6
  const ZOOM_UNIT = 0.9;
8
7
  const MAX_ZOOM = 4;
9
8
  const MIN_ZOOM = 0.5;
10
- const { width, height } = useResizeObserver({ ref: canvasRef });
11
- const [initZoom, setInitZoom] = useState(0);
12
- const [zoom, setZoom] = useState(0);
13
- const [zoomX, setZoomX] = useState(0);
14
- const [zoomY, setZoomY] = useState(0);
15
- const [dx, setDx] = useState(0);
16
- const [dy, setDy] = useState(0);
17
- const [dw, setDw] = useState(0);
18
- const [dh, setDh] = useState(0);
19
- const [moveX, setMoveX] = useState(0);
20
- const [moveY, setMoveY] = useState(0);
9
+ const [canvasState, setCanvasState] = useState({
10
+ dx: 0,
11
+ dy: 0,
12
+ dw: 0,
13
+ dh: 0,
14
+ moveX: 0,
15
+ moveY: 0,
16
+ zoomX: 0,
17
+ zoomY: 0,
18
+ zoom: 0,
19
+ initZoom: 0,
20
+ });
21
21
  const [status, setStatus] = useState('');
22
22
  const [imageOnloadCount, setImageOnloadCount] = useState(0);
23
23
  const [startMousePoint, setStartMousePoint] = useState();
24
- const calculatorZoomPoint = useCallback((canvasEl, movementX, movementY, dx, dy) => {
25
- let x = 0;
26
- let y = 0;
27
- if (canvasRef.current) {
28
- x = -dx - movementX + canvasEl.width / 2;
29
- y = -dy - movementY + canvasEl.height / 2;
30
- }
31
- return { x, y };
32
- }, [canvasRef]);
33
- const calculateInitZoom = useCallback((canvasEl, image) => {
34
- if (!image)
35
- return 1;
36
- const canvasRatio = canvasEl.clientWidth / canvasEl.clientHeight;
37
- const imageRatio = image.naturalWidth / image.naturalHeight;
38
- return canvasRatio < imageRatio
39
- ? canvasEl.clientWidth / image.naturalWidth
40
- : canvasEl.clientHeight / image.naturalHeight;
41
- }, []);
42
- const init = (canvasEl, image) => {
43
- const point = canvasCenterPoint(canvasEl, image);
44
- setDx(point.x);
45
- setDy(point.y);
46
- setDw(image.width);
47
- setDh(image.height);
48
- setMoveX(0);
49
- setMoveY(0);
50
- const init_zoom = calculateInitZoom(canvasEl, image);
51
- setZoom(init_zoom);
52
- setInitZoom(init_zoom);
53
- const zoomPoint = calculatorZoomPoint(canvasEl, 0, 0, point.x, point.y);
54
- setZoomX(zoomPoint.x);
55
- setZoomY(zoomPoint.y);
24
+ const init = (canvas, image) => {
25
+ const point = canvasCenterPoint(canvas, image);
26
+ const zoomPoint = calculatorZoomPoint(canvas, 0, 0, point.x, point.y);
27
+ const initZoomValue = calculateInitZoom(canvas, image);
28
+ setCanvasState({
29
+ dx: point.x,
30
+ dy: point.y,
31
+ dw: image.width,
32
+ dh: image.height,
33
+ moveX: 0,
34
+ moveY: 0,
35
+ zoomX: zoomPoint.x,
36
+ zoomY: zoomPoint.y,
37
+ zoom: initZoomValue,
38
+ initZoom: initZoomValue,
39
+ });
40
+ };
41
+ const preserveZoomAndPosition = (canvas, image, zoomRatio) => {
42
+ const prevZoomRatio = zoomRatio || 1;
43
+ const point = canvasCenterPoint(canvas, image);
44
+ const newInitZoom = calculateInitZoom(canvas, image);
45
+ const zoomPoint = calculatorZoomPoint(canvas, canvasState.moveX, canvasState.moveY, point.x, point.y);
46
+ setCanvasState({
47
+ dx: point.x,
48
+ dy: point.y,
49
+ dw: image.width,
50
+ dh: image.height,
51
+ moveX: canvasState.moveX,
52
+ moveY: canvasState.moveY,
53
+ zoomX: zoomPoint.x,
54
+ zoomY: zoomPoint.y,
55
+ zoom: newInitZoom * prevZoomRatio,
56
+ initZoom: newInitZoom,
57
+ });
56
58
  };
57
59
  const initCanvas = () => {
58
- if (imageRef?.current) {
59
- const canvasEl = resolutionCanvas(canvasRef.current);
60
- if (canvasEl)
61
- init(canvasEl, imageRef.current);
62
- setImageOnloadCount((prev) => prev + 1);
63
- }
60
+ if (!canvasRef.current)
61
+ 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);
64
76
  };
65
77
  const handleWheel = (event) => {
66
- if (!panZoomable || !canvasRef.current || !imageRef?.current)
78
+ if (!panZoomable || !canvasRef.current || canvasState.initZoom <= 0)
67
79
  return;
68
- let calc_zoom = event.deltaY < 0 ? (zoom || 1) * (1 / ZOOM_UNIT) : (zoom || 1) * ZOOM_UNIT;
69
- if (initZoom * MAX_ZOOM < zoom * ZOOM_UNIT)
70
- calc_zoom = calc_zoom * ZOOM_UNIT;
71
- if (initZoom * MIN_ZOOM > zoom * ZOOM_UNIT)
72
- calc_zoom = calc_zoom * (1 / ZOOM_UNIT);
73
- const zoomPoint = calculatorZoomPoint(canvasRef.current, moveX, moveY, dx, dy);
74
- setZoomX(zoomPoint.x);
75
- setZoomY(zoomPoint.y);
76
- setZoom(calc_zoom);
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);
85
+ const zoomPoint = calculatorZoomPoint(canvasRef.current, canvasState.moveX, canvasState.moveY, canvasState.dx, canvasState.dy);
86
+ setCanvasState({
87
+ ...canvasState,
88
+ zoomX: zoomPoint.x,
89
+ zoomY: zoomPoint.y,
90
+ zoom: calcZoom,
91
+ });
77
92
  };
78
93
  const handleMouseMove = (event) => {
79
- if (!panZoomable || status !== MouseStatus.MOVE || !canvasRef.current || !imageRef?.current)
94
+ if (!panZoomable || !canvasRef.current || status !== MouseStatus.MOVE)
80
95
  return;
81
- const canvasEl = canvasRef.current;
82
- const rect = canvasEl.getBoundingClientRect();
96
+ const canvas = canvasRef.current;
97
+ const rect = canvas.getBoundingClientRect();
83
98
  const mouseX = event.clientX - rect.left;
84
99
  const mouseY = event.clientY - rect.top;
85
- const mouse = getMousePointTransform({ x: mouseX, y: mouseY }, { x: moveX, y: moveY }, { x: zoomX, y: zoomY }, { x: dx, y: dy }, zoom || 1, canvasEl);
100
+ const mouse = getMousePointTransform({ x: mouseX, y: mouseY }, { x: canvasState.moveX, y: canvasState.moveY }, { x: canvasState.zoomX, y: canvasState.zoomY }, { x: canvasState.dx, y: canvasState.dy }, canvasState.zoom || 1, canvas);
86
101
  let x = mouse.x;
87
102
  let y = mouse.y;
88
103
  if (startMousePoint) {
89
104
  x -= startMousePoint.x;
90
105
  y -= startMousePoint.y;
91
106
  }
92
- setMoveX(moveX + x);
93
- setMoveY(moveY + y);
94
- const zoomPoint = calculatorZoomPoint(canvasEl, moveX + x, moveY + y, dx, dy);
95
- setZoomX(zoomPoint.x);
96
- setZoomY(zoomPoint.y);
107
+ const zoomPoint = calculatorZoomPoint(canvas, canvasState.moveX + x, canvasState.moveY + y, canvasState.dx, canvasState.dy);
108
+ setCanvasState({
109
+ ...canvasState,
110
+ moveX: canvasState.moveX + x,
111
+ moveY: canvasState.moveY + y,
112
+ zoomX: zoomPoint.x,
113
+ zoomY: zoomPoint.y,
114
+ });
97
115
  event.preventDefault();
98
116
  };
99
117
  const handleMouseDown = (event) => {
100
- if (!panZoomable || !canvasRef.current || !imageRef?.current)
118
+ if (!panZoomable || !canvasRef.current)
101
119
  return;
102
120
  setStatus(MouseStatus.MOVE);
103
- const canvasEl = canvasRef.current;
104
- const rect = canvasEl.getBoundingClientRect();
121
+ const canvas = canvasRef.current;
122
+ const rect = canvas.getBoundingClientRect();
105
123
  const mouseX = event.clientX - rect.left;
106
124
  const mouseY = event.clientY - rect.top;
107
- const mouse = getMousePointTransform({ x: mouseX, y: mouseY }, { x: moveX, y: moveY }, { x: zoomX, y: zoomY }, { x: dx, y: dy }, zoom || 1, canvasEl);
125
+ const mouse = getMousePointTransform({ x: mouseX, y: mouseY }, { x: canvasState.moveX, y: canvasState.moveY }, { x: canvasState.zoomX, y: canvasState.zoomY }, { x: canvasState.dx, y: canvasState.dy }, canvasState.zoom || 1, canvas);
108
126
  setStartMousePoint({ x: mouse.x, y: mouse.y });
109
127
  event.preventDefault();
110
128
  };
111
129
  const handleMouseUp = (event) => {
112
- if (!panZoomable)
130
+ if (!panZoomable || !canvasRef.current)
113
131
  return;
114
132
  setStatus(MouseStatus.STOP);
115
133
  event.preventDefault();
116
134
  };
117
135
  const handleMouseLeave = (event) => {
118
- if (!panZoomable)
136
+ if (!panZoomable || !canvasRef.current)
119
137
  return;
120
138
  setStatus(MouseStatus.STOP);
121
139
  event.preventDefault();
122
140
  };
123
- useEffect(() => {
124
- const redraw = () => {
125
- if (canvasRef.current && imageRef?.current) {
126
- drawCanvas(moveX, moveY, zoomX, zoomY, zoom, dx, dy, resolutionCanvas(canvasRef.current), imageRef.current, true);
127
- }
128
- };
129
- redraw();
130
- }, [moveX, moveY, zoomX, zoomY, zoom, dx, dy, dw, dh, imageOnloadCount, canvasRef, imageRef]);
131
- useEffect(() => {
132
- if (!imageRef?.current || !canvasRef?.current || !width || !height)
133
- return;
134
- const canvasEl = resolutionCanvas(canvasRef.current);
135
- if (!canvasEl)
136
- return;
137
- const isFirstLoad = imageOnloadCount === 0;
138
- if (isFirstLoad) {
139
- init(canvasEl, imageRef.current);
140
- setImageOnloadCount((prev) => prev + 1);
141
- return;
142
- }
143
- if (resetOnImageChange) {
144
- init(canvasEl, imageRef.current);
145
- }
146
- else {
147
- drawCanvas(moveX, moveY, zoomX, zoomY, zoom, dx, dy, canvasEl, imageRef.current, true);
148
- }
149
- setImageOnloadCount((prev) => prev + 1);
150
- }, [imageRef?.current, width, height, resetOnImageChange]);
151
141
  return {
152
- moveX,
153
- moveY,
154
- zoom,
155
- initZoom,
156
- zoomX,
157
- zoomY,
158
- dx,
159
- dy,
160
- dw,
161
- dh,
142
+ canvasState,
143
+ imageOnloadCount,
144
+ setImageOnloadCount,
145
+ init,
146
+ initCanvas,
147
+ clearCanvas,
148
+ preserveZoomAndPosition,
162
149
  handleWheel,
163
150
  handleMouseDown,
164
151
  handleMouseMove,
165
152
  handleMouseUp,
166
153
  handleMouseLeave,
167
- initCanvas,
168
154
  };
169
- };
155
+ }
@@ -14,14 +14,14 @@ const useResizeObserver = (opts = {}) => {
14
14
  didUnmount.current = true;
15
15
  };
16
16
  }, []);
17
- const refCallback = useResolvedElement(useCallback(element => {
17
+ const refCallback = useResolvedElement(useCallback((element) => {
18
18
  if (!resizeObserverRef.current ||
19
19
  resizeObserverRef.current.box !== opts.box ||
20
20
  resizeObserverRef.current.round !== round) {
21
21
  resizeObserverRef.current = {
22
22
  box: opts.box,
23
23
  round,
24
- instance: new ResizeObserver(entries => {
24
+ instance: new ResizeObserver((entries) => {
25
25
  const entry = entries[0];
26
26
  const boxProp = opts.box === 'border-box'
27
27
  ? 'borderBoxSize'
@@ -102,7 +102,7 @@ function useResolvedElement(subscriber, refOrElement) {
102
102
  }
103
103
  };
104
104
  }, []);
105
- return useCallback(element => {
105
+ return useCallback((element) => {
106
106
  cbElementRef.current = element;
107
107
  evaluateSubscription();
108
108
  }, [evaluateSubscription]);
@@ -3,6 +3,11 @@ export declare const canvasCenterPoint: (canvas: HTMLCanvasElement, image: HTMLI
3
3
  x: number;
4
4
  y: number;
5
5
  };
6
+ export declare const calculatorZoomPoint: (canvas: HTMLCanvasElement, movementX: number, movementY: number, dx: number, dy: number) => {
7
+ x: number;
8
+ y: number;
9
+ };
10
+ export declare const calculateInitZoom: (canvas: HTMLCanvasElement, image: HTMLImageElement) => number;
6
11
  export declare const drawCanvas: (moveX: number, moveY: number, zoomX: number, zoomY: number, zoom: number, dx: number, dy: number, canvas_el?: HTMLCanvasElement, image?: HTMLImageElement, isClear?: boolean) => void;
7
12
  export declare const drawRect: (context: CanvasRenderingContext2D, coordinate: Coordinate) => void;
8
13
  export declare const setRectangleStyle: (context: CanvasRenderingContext2D, coordinate: Coordinate) => void;
@@ -5,6 +5,22 @@ export const canvasCenterPoint = (canvas, image) => {
5
5
  const centerY = canvas.height / 2 - image.height / 2;
6
6
  return { x: centerX, y: centerY };
7
7
  };
8
+ export const calculatorZoomPoint = (canvas, movementX, movementY, dx, dy) => {
9
+ let x = 0;
10
+ let y = 0;
11
+ if (canvas) {
12
+ x = -dx - movementX + canvas.width / 2;
13
+ y = -dy - movementY + canvas.height / 2;
14
+ }
15
+ return { x, y };
16
+ };
17
+ export const calculateInitZoom = (canvas, image) => {
18
+ if (!canvas || !image.naturalWidth)
19
+ return 1;
20
+ const canvasRatio = canvas.clientWidth / canvas.clientHeight;
21
+ const imageRatio = image.naturalWidth / image.naturalHeight;
22
+ return canvasRatio < imageRatio ? canvas.clientWidth / image.naturalWidth : canvas.clientHeight / image.naturalHeight;
23
+ };
8
24
  // [캔버스] 그리기
9
25
  export const drawCanvas = (moveX, moveY, zoomX, zoomY, zoom, dx, dy, canvas_el, image, isClear) => {
10
26
  if (canvas_el) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepnoid/canvas",
3
- "version": "0.1.29",
3
+ "version": "0.1.31",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.cjs",
@@ -1,6 +0,0 @@
1
- import { RefObject } from 'react';
2
- export declare const useImageLoader: (src: string, imageRef: RefObject<HTMLImageElement>, timeout?: number, onLoadSuccess?: () => void, onLoadError?: (error: Error) => void) => {
3
- image: HTMLImageElement | null;
4
- isLoading: boolean;
5
- error: string | null;
6
- };
@@ -1,71 +0,0 @@
1
- 'use client';
2
- import { useEffect, useState } from 'react';
3
- export const useImageLoader = (src, imageRef, timeout = 10000, onLoadSuccess, onLoadError) => {
4
- const [image, setImage] = useState(null);
5
- const [isLoading, setIsLoading] = useState(false);
6
- const [error, setError] = useState(null);
7
- useEffect(() => {
8
- if (!src || src.trim() === '') {
9
- setImage(null);
10
- setIsLoading(false);
11
- setError(null);
12
- return;
13
- }
14
- if (imageRef.current && imageRef.current.src === src) {
15
- setImage(imageRef.current);
16
- setIsLoading(false);
17
- setError(null);
18
- onLoadSuccess?.();
19
- return;
20
- }
21
- setIsLoading(true);
22
- setError(null);
23
- setImage(null);
24
- const controller = new AbortController();
25
- const { signal } = controller;
26
- const img = new Image();
27
- img.crossOrigin = 'anonymous';
28
- img.src = src;
29
- const timeoutId = setTimeout(() => {
30
- if (!signal.aborted) {
31
- controller.abort();
32
- const e = new Error(`Image load timeout: ${src}`);
33
- setError(e.message);
34
- setIsLoading(false);
35
- onLoadError?.(e);
36
- }
37
- }, timeout);
38
- const handleLoad = () => {
39
- if (signal.aborted)
40
- return;
41
- clearTimeout(timeoutId);
42
- imageRef.current = img;
43
- setImage(img);
44
- setIsLoading(false);
45
- setError(null);
46
- onLoadSuccess?.();
47
- };
48
- const handleError = (e) => {
49
- if (signal.aborted)
50
- return;
51
- clearTimeout(timeoutId);
52
- const err = e instanceof Error ? e : new Error(`Failed to load image: ${src}`);
53
- setError(err.message);
54
- setIsLoading(false);
55
- onLoadError?.(err);
56
- };
57
- img.onload = handleLoad;
58
- img.onerror = handleError;
59
- return () => {
60
- clearTimeout(timeoutId);
61
- controller.abort();
62
- img.onload = null;
63
- img.onerror = null;
64
- try {
65
- img.src = '';
66
- }
67
- catch { }
68
- };
69
- }, [src, imageRef, timeout, onLoadSuccess, onLoadError]);
70
- return { image, isLoading, error };
71
- };