@deepnoid/canvas 0.1.85 → 0.1.86
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 +15 -1
- package/package.json +3 -2
- package/skills/deepnoid-canvas/DEPLOY.md +57 -0
- package/skills/deepnoid-canvas/README.md +56 -0
- package/skills/deepnoid-canvas/SKILL.md +131 -0
- package/skills/deepnoid-canvas/references/apply-style.md +189 -0
- package/skills/deepnoid-canvas/references/components.md +197 -0
- package/skills/deepnoid-canvas/references/types.md +231 -0
- package/skills/deepnoid-canvas/rules/annotations.md +152 -0
- package/skills/deepnoid-canvas/rules/events.md +124 -0
- package/skills/deepnoid-canvas/rules/layout.md +171 -0
- package/skills/deepnoid-canvas/rules/rendering.md +142 -0
- package/skills/deepnoid-canvas/setup.md +147 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# applyStyle 렌더링 규칙
|
|
2
|
+
|
|
3
|
+
## 규칙 1: applyStyle은 반드시 제공해야 한다
|
|
4
|
+
|
|
5
|
+
`drawing.applyStyle`은 필수 prop입니다. 이 함수 없이는 어노테이션이 화면에 표시되지 않습니다.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
// ✅ 올바른 사용
|
|
9
|
+
<AnnotationEditor
|
|
10
|
+
drawing={{
|
|
11
|
+
lineSize: 2,
|
|
12
|
+
applyStyle: myStyleFunction,
|
|
13
|
+
mode: DrawMode.RECTANGLE,
|
|
14
|
+
color: '#FF4136',
|
|
15
|
+
}}
|
|
16
|
+
...
|
|
17
|
+
/>
|
|
18
|
+
|
|
19
|
+
// ❌ 잘못된 사용 — applyStyle 누락
|
|
20
|
+
<AnnotationEditor
|
|
21
|
+
drawing={{
|
|
22
|
+
lineSize: 2,
|
|
23
|
+
mode: DrawMode.RECTANGLE,
|
|
24
|
+
}}
|
|
25
|
+
...
|
|
26
|
+
/>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 규칙 2: variant별로 분기 처리해야 한다
|
|
30
|
+
|
|
31
|
+
`applyStyle`은 `drawRect`과 `drawText` 두 번 호출됩니다. 각 variant에 맞는 Canvas2D 속성을 설정해야 합니다.
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
// ✅ 올바른 사용 — variant별 분기
|
|
35
|
+
const applyStyle: ApplyAnnotationStyle = ({ variant, context, annotation, drawLineSize, zoom }) => {
|
|
36
|
+
if (variant === 'drawRect') {
|
|
37
|
+
context.strokeStyle = '#FF4136';
|
|
38
|
+
context.lineWidth = drawLineSize / zoom;
|
|
39
|
+
if (annotation.type === DrawMode.RECTANGLE) {
|
|
40
|
+
context.strokeRect(annotation.x, annotation.y, annotation.width, annotation.height);
|
|
41
|
+
} else if (annotation.type === DrawMode.POLYGON) {
|
|
42
|
+
context.beginPath();
|
|
43
|
+
annotation.points.forEach((p, i) => i === 0 ? context.moveTo(p.x, p.y) : context.lineTo(p.x, p.y));
|
|
44
|
+
context.closePath();
|
|
45
|
+
context.stroke();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (variant === 'drawText') {
|
|
49
|
+
context.fillStyle = '#FF4136';
|
|
50
|
+
context.font = `bold ${14 / zoom}px sans-serif`;
|
|
51
|
+
// 텍스트 렌더링 로직...
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// ❌ 잘못된 사용 — variant 무시
|
|
56
|
+
const applyStyle: ApplyAnnotationStyle = ({ context, annotation }) => {
|
|
57
|
+
context.strokeStyle = '#FF4136';
|
|
58
|
+
context.strokeRect(annotation.x, annotation.y, annotation.width, annotation.height);
|
|
59
|
+
};
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 규칙 3: 모든 크기 값에 zoom 보정을 적용해야 한다
|
|
63
|
+
|
|
64
|
+
줌 배율이 변해도 어노테이션 선 두께, 폰트 크기, 대시 간격 등이 일정하게 보이려면 반드시 `/ zoom`으로 나누어야 합니다.
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
// ✅ 올바른 사용 — zoom 보정
|
|
68
|
+
context.lineWidth = drawLineSize / zoom;
|
|
69
|
+
context.font = `bold ${14 / zoom}px sans-serif`;
|
|
70
|
+
context.setLineDash([6 / zoom, 3 / zoom]);
|
|
71
|
+
|
|
72
|
+
// ❌ 잘못된 사용 — zoom 보정 없음 (줌인하면 선이 점점 두꺼워짐)
|
|
73
|
+
context.lineWidth = drawLineSize;
|
|
74
|
+
context.font = `bold 14px sans-serif`;
|
|
75
|
+
context.setLineDash([6, 3]);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## 규칙 4: annotation.type으로 분기하여 도형을 직접 그려야 한다
|
|
79
|
+
|
|
80
|
+
`applyStyle`의 `drawRect` variant에서는 스타일 설정뿐 아니라 **실제 도형 그리기**까지 소비자가 담당합니다.
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
// ✅ 올바른 사용 — 타입별 도형 그리기
|
|
84
|
+
if (variant === 'drawRect') {
|
|
85
|
+
context.strokeStyle = '#FF4136';
|
|
86
|
+
context.lineWidth = drawLineSize / zoom;
|
|
87
|
+
|
|
88
|
+
if (annotation.type === DrawMode.RECTANGLE) {
|
|
89
|
+
const { x, y, width, height } = annotation;
|
|
90
|
+
context.strokeRect(x, y, width, height);
|
|
91
|
+
} else if (annotation.type === DrawMode.POLYGON) {
|
|
92
|
+
context.beginPath();
|
|
93
|
+
annotation.points.forEach((p, idx) => {
|
|
94
|
+
if (idx === 0) context.moveTo(p.x, p.y);
|
|
95
|
+
else context.lineTo(p.x, p.y);
|
|
96
|
+
});
|
|
97
|
+
context.closePath();
|
|
98
|
+
context.stroke();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ❌ 잘못된 사용 — RECTANGLE만 처리하고 POLYGON 무시
|
|
103
|
+
if (variant === 'drawRect') {
|
|
104
|
+
context.strokeRect(annotation.x, annotation.y, annotation.width, annotation.height);
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 규칙 5: setLineDash 사용 후 반드시 초기화해야 한다
|
|
109
|
+
|
|
110
|
+
Canvas2D 컨텍스트는 상태를 유지합니다. `setLineDash`로 점선을 설정하면 다음 어노테이션에도 영향을 줍니다.
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
// ✅ 올바른 사용 — 조건부 dash + 초기화
|
|
114
|
+
if (annotation.selected) {
|
|
115
|
+
context.setLineDash([6 / zoom, 3 / zoom]);
|
|
116
|
+
} else {
|
|
117
|
+
context.setLineDash([]);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ❌ 잘못된 사용 — selected일 때만 dash 설정, else 빠짐
|
|
121
|
+
if (annotation.selected) {
|
|
122
|
+
context.setLineDash([6 / zoom, 3 / zoom]);
|
|
123
|
+
}
|
|
124
|
+
// → 다음 어노테이션도 점선으로 그려짐
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## 규칙 6: annotation.color를 활용한 상태별 스타일링
|
|
128
|
+
|
|
129
|
+
어노테이션의 `color` 필드(`'success' | 'warning' | 'danger'`)를 활용하여 상태별 색상을 적용할 수 있습니다.
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
// ✅ 올바른 사용 — color 필드 매핑
|
|
133
|
+
const colorMap = {
|
|
134
|
+
success: 'rgba(36, 162, 91, 1)',
|
|
135
|
+
warning: 'rgba(237, 164, 16, 1)',
|
|
136
|
+
danger: 'rgba(255, 70, 132, 1)',
|
|
137
|
+
};
|
|
138
|
+
context.strokeStyle = colorMap[annotation.color ?? 'danger'];
|
|
139
|
+
|
|
140
|
+
// ❌ 잘못된 사용 — color 필드를 직접 CSS 색상으로 사용
|
|
141
|
+
context.strokeStyle = annotation.color; // 'danger'는 유효한 CSS 색상이 아님
|
|
142
|
+
```
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# 소비자 프로젝트 설정 가이드
|
|
2
|
+
|
|
3
|
+
## 1. 설치
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# npm
|
|
7
|
+
npm install @deepnoid/canvas
|
|
8
|
+
|
|
9
|
+
# yarn
|
|
10
|
+
yarn add @deepnoid/canvas
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
**Peer dependency**: React 19.0.0 이상 필수.
|
|
14
|
+
|
|
15
|
+
## 2. Claude Code 스킬 설치
|
|
16
|
+
|
|
17
|
+
패키지에 포함된 스킬을 프로젝트의 `.claude/skills/`에 복사합니다.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
cp -r node_modules/@deepnoid/canvas/skills/deepnoid-canvas .claude/skills/
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
이후 Claude Code가 `@deepnoid/canvas` 관련 코드를 작성할 때 자동으로 스킬을 참조합니다.
|
|
24
|
+
|
|
25
|
+
## 3. 기본 설정
|
|
26
|
+
|
|
27
|
+
### 최소 구성 (Editor)
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { useState } from 'react';
|
|
31
|
+
import { AnnotationEditor, DrawMode } from '@deepnoid/canvas';
|
|
32
|
+
import type { Annotation, ApplyAnnotationStyle } from '@deepnoid/canvas';
|
|
33
|
+
|
|
34
|
+
// Step 1: applyStyle 함수 정의 (필수)
|
|
35
|
+
const applyStyle: ApplyAnnotationStyle = ({ variant, context, annotation, drawLineSize, zoom }) => {
|
|
36
|
+
if (variant === 'drawRect') {
|
|
37
|
+
context.strokeStyle = annotation.selected ? '#00FF00' : '#FF4136';
|
|
38
|
+
context.lineWidth = drawLineSize / zoom;
|
|
39
|
+
if (annotation.type === DrawMode.RECTANGLE) {
|
|
40
|
+
context.strokeRect(annotation.x, annotation.y, annotation.width, annotation.height);
|
|
41
|
+
} else if (annotation.type === DrawMode.POLYGON) {
|
|
42
|
+
context.beginPath();
|
|
43
|
+
annotation.points.forEach((p, i) => i === 0 ? context.moveTo(p.x, p.y) : context.lineTo(p.x, p.y));
|
|
44
|
+
context.closePath();
|
|
45
|
+
context.stroke();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Step 2: 컴포넌트 사용
|
|
51
|
+
function AnnotationPage() {
|
|
52
|
+
const [annotations, setAnnotations] = useState<Annotation[]>([]);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
// Step 3: 반드시 고정 크기의 컨테이너로 감싸기
|
|
56
|
+
<div style={{ width: '100%', height: '100vh' }}>
|
|
57
|
+
<AnnotationEditor
|
|
58
|
+
image="https://example.com/image.jpg"
|
|
59
|
+
annotations={annotations}
|
|
60
|
+
setAnnotations={setAnnotations}
|
|
61
|
+
drawing={{
|
|
62
|
+
mode: DrawMode.RECTANGLE,
|
|
63
|
+
color: '#FF4136',
|
|
64
|
+
lineSize: 2,
|
|
65
|
+
applyStyle,
|
|
66
|
+
}}
|
|
67
|
+
events={{}}
|
|
68
|
+
enableHotkeys
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 최소 구성 (Viewer)
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { AnnotationViewer } from '@deepnoid/canvas';
|
|
79
|
+
|
|
80
|
+
function ViewerPage({ annotations }) {
|
|
81
|
+
return (
|
|
82
|
+
<div style={{ width: '100%', height: '100vh' }}>
|
|
83
|
+
<AnnotationViewer
|
|
84
|
+
image="https://example.com/image.jpg"
|
|
85
|
+
annotations={annotations}
|
|
86
|
+
drawing={{ lineSize: 2, applyStyle }}
|
|
87
|
+
events={{}}
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 4. 권장 설정
|
|
95
|
+
|
|
96
|
+
### 줌/팬 활성화
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
<AnnotationEditor
|
|
100
|
+
options={{
|
|
101
|
+
panZoomEnabled: true,
|
|
102
|
+
zoom: { min: 0.5, max: 4, step: 0.9 },
|
|
103
|
+
}}
|
|
104
|
+
...
|
|
105
|
+
/>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### annotationSelected 이벤트 연동
|
|
109
|
+
|
|
110
|
+
어노테이션 클릭 시 그리기 모드 자동 전환:
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
const [mode, setMode] = useState(DrawMode.RECTANGLE);
|
|
114
|
+
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
const handler = (e: CustomEvent) => {
|
|
117
|
+
if (e.detail?.type) setMode(e.detail.type);
|
|
118
|
+
};
|
|
119
|
+
window.addEventListener('annotationSelected', handler);
|
|
120
|
+
return () => window.removeEventListener('annotationSelected', handler);
|
|
121
|
+
}, []);
|
|
122
|
+
|
|
123
|
+
<AnnotationEditor drawing={{ mode, ... }} ... />
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### ref를 통한 줌 리셋 버튼
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
import { useRef } from 'react';
|
|
130
|
+
import type { AnnotationCanvasHandle } from '@deepnoid/canvas';
|
|
131
|
+
|
|
132
|
+
const canvasRef = useRef<AnnotationCanvasHandle>(null);
|
|
133
|
+
|
|
134
|
+
<button onClick={() => canvasRef.current?.resetImageSize()}>초기화</button>
|
|
135
|
+
<AnnotationEditor ref={canvasRef} ... />
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## 5. 체크리스트
|
|
139
|
+
|
|
140
|
+
소비자 프로젝트 설정 후 확인 사항:
|
|
141
|
+
|
|
142
|
+
- [ ] React 19+ 설치 확인
|
|
143
|
+
- [ ] `applyStyle` 함수에서 `drawRect`/`drawText` variant 분기 처리
|
|
144
|
+
- [ ] `applyStyle` 내 모든 크기 값에 `/ zoom` 보정 적용
|
|
145
|
+
- [ ] 컨테이너 `div`에 고정 크기(width/height) 설정
|
|
146
|
+
- [ ] RECTANGLE/POLYGON 모두에서 도형 그리기 처리
|
|
147
|
+
- [ ] `events` prop에 최소 빈 객체 `{}` 전달
|