@deepnoid/canvas
v0.1.86
Published
이 프로젝트는 본부 내 프로젝트에서 사용되는 공통 CANVAS 라이브러리입니다.
Downloads
419
Readme
deepnoid-canvas
이 프로젝트는 본부 내 프로젝트에서 사용되는 공통 CANVAS 라이브러리입니다.
개요
Canvas 기반 이미지 annotation 라이브러리로, TypeScript Engine + React Host 구조로 설계되었습니다.
- Engine (TS): Canvas 조작, 상태 관리, 이벤트 처리 등 핵심 로직
- React: UI 렌더링, DOM ref 관리, Engine 생명주기 관리
주요 기능
- ✏️ Annotation 지원 (그리기 및 편집)
- Rectangle (BBox):
- 모서리/상하좌우 테두리를 통한 직관적 크기 조절
- 영역 내부 드래그(
move커서)를 통한 도형 전체 이동
- Polygon:
- 다각형 점찍기 및 시작점(또는 더블클릭) 클릭으로 도형 완성
- 선 분할(Edge Point Insertion): 기존 그려진 폴리곤 선(Edge) 위에 마우스를 1초간 올려둘 시, 새로운 꼭짓점(Vertex)을 간편하게 추가 가능 (클릭/이동 오작동 방지)
- 내부 클릭 후 드래그하여 전체 다각형 이동 지원
- 스마트 UI 동기화: 캔버스 내의 도형 클릭 시 선택된 객체 타입(RECTANGLE/POLYGON)에 맞춰 외부 도구(React 상태)가 자동으로 전환되도록 이벤트(
annotationSelected) 연동
- Rectangle (BBox):
- 👁️ Viewer 모드 지원 (읽기 전용)
- 🔍 Pan & Zoom 지원 (Image Canvas / Annotation Canvas 레이어 완전 분리)
- ↩️ Undo/Redo 기능 (History 상태망 자체 관리)
- ⌨️ 단축키 지원 (선택 객체 삭제, 되돌리기, 한 객체만 보기 등)
- 🎨 커스터마이징 가능한 스타일링 (커스텀 렌더링 함수(
applyStyle) 주입 가능)
빠른 시작
설치
npm install @deepnoid/canvasClaude Code 스킬 설치 (선택)
소비자 프로젝트에서 Claude Code가 @deepnoid/canvas API를 정확하게 사용할 수 있도록 스킬을 복사합니다.
# .claude/skills 디렉토리가 없으면 생성
mkdir -p .claude/skills
# 스킬 복사
cp -r node_modules/@deepnoid/canvas/skills/deepnoid-canvas .claude/skills/복사 후 Claude Code에서 @deepnoid/canvas를 import하면 자동으로 올바른 사용법, Props, applyStyle 작성법 등을 안내합니다.
기본 사용법
import { AnnotationEditor } from 'deepnoid-canvas';
function App() {
const [annotations, setAnnotations] = useState([]);
return (
<AnnotationEditor
image='https://example.com/image.jpg'
annotations={annotations}
setAnnotations={setAnnotations}
drawing={{
mode: 'RECTANGLE',
color: '#FF4136',
lineSize: 2,
}}
options={{
panZoomEnabled: true,
zoom: { min: 0.5, max: 4, step: 0.9 },
}}
enableHotkeys
/>
);
}Props
AnnotationEditor
Editor 모드로 annotation을 생성하고 편집할 수 있습니다.
| Prop | Type | Required | Description |
| ---------------- | ------------------------------------- | -------- | -------------------------------- |
| image | string | ✅ | 이미지 URL |
| drawing | AnnotationCanvasDrawing | ✅ | 그리기 모드 및 스타일 설정 |
| annotations | Annotation[] | - | Annotation 목록 |
| setAnnotations | (annotations: Annotation[]) => void | - | Annotation 변경 시 호출되는 콜백 |
| options | AnnotationCanvasOptions | - | 줌/팬 및 기타 옵션 |
| events | AnnotationCanvasEvents | - | 이미지 로드 관련 이벤트 핸들러 |
| enableHotkeys | boolean | - | 단축키 활성화 여부 |
AnnotationViewer
Viewer 모드로 annotation을 읽기 전용으로 표시합니다.
| Prop | Type | Required | Description |
| --------------- | -------------------------------------------------------------------------- | -------- | ------------------------------ |
| image | string | ✅ | 이미지 URL |
| drawing | Pick<AnnotationCanvasDrawing, 'lineSize' \| 'applyStyle'> | ✅ | 렌더링 스타일 설정 (mode 제외) |
| annotations | Annotation[] | - | 표시할 Annotation 목록 |
| options | AnnotationCanvasOptions | - | 줌/팬 및 기타 옵션 |
| events | Pick<AnnotationCanvasEvents, 'onImageLoadSuccess' \| 'onImageLoadError'> | - | 이미지 로드 관련 이벤트 핸들러 |
| enableHotkeys | boolean | - | 단축키 활성화 여부 |
import { AnnotationViewer } from 'deepnoid-canvas';
function App() {
const [annotations] = useState([{ type: 'RECTANGLE', x: 100, y: 100, width: 200, height: 150 }]);
return (
<AnnotationViewer
image='https://example.com/image.jpg'
annotations={annotations}
drawing={{
lineSize: 2,
applyStyle: (params) => {
// 커스텀 스타일 적용
},
}}
options={{
panZoomEnabled: true,
}}
/>
);
}Drawing 설정
type AnnotationCanvasDrawing = {
mode?: 'RECTANGLE' | 'POLYGON' | 'NONE'; // 그리기 모드 (Editor 전용)
color?: string; // Annotation 색상 (HEX)
lineSize: number; // 선 두께
label?: Label; // Annotation에 표시할 라벨
applyStyle: ApplyAnnotationStyle; // 커스텀 스타일 함수
};
type Label = {
id: number;
name: string;
type: string;
};
type ApplyAnnotationStyle = (params: {
variant: 'drawRect' | 'drawText';
context: CanvasRenderingContext2D;
annotation: Annotation;
drawLineSize: number;
zoom: number;
}) => void;Options 설정
type AnnotationCanvasOptions = {
panZoomEnabled?: boolean; // 줌/팬 기능 활성화 (기본: false)
zoom?: {
min?: number; // 최소 줌 배율 (기본: 0.1)
max?: number; // 최대 줌 배율 (기본: Infinity)
step?: number; // 줌 단계 (기본: 0.9)
};
ZoomButton?: ComponentType; // 커스텀 줌 버튼 컴포넌트
resetOnImageChange?: boolean; // 이미지 변경 시 줌 리셋 여부
};Events 설정
type AnnotationCanvasEvents = {
onImageLoadSuccess?: () => void; // 이미지 로드 성공 시
onImageLoadError?: (error: Error) => void; // 이미지 로드 실패 시
};단축키
| 키 | 기능 |
| -------------- | ----------------------------- |
| Delete | 선택한 annotation 삭제 |
| Ctrl+Z | Undo |
| Ctrl+Shift+Z | Redo |
| X | 선택한 annotation만 보기 토글 |
아키텍처
Facade 패턴
React Component
↓
AnnotationEngine (Facade)
↓ ↓ ↓ ↓ ↓
│ │ │ │ └─ History (Undo/Redo)
│ │ │ └─── PanZoomController
│ │ └───── InteractionController
│ └─────── Renderers
└───────── Annotation Controllers디렉토리 구조
src/
├─ engine/ # TypeScript Engine (순수 로직)
│ ├─ annotation/ # Annotation 타입별 로직
│ ├─ interaction/ # 이벤트 처리
│ ├─ pan-zoom/ # 줌/이동 기능
│ ├─ public/ # 외부 공개 API
│ └─ renderer/ # Canvas 렌더링
│
└─ react/ # React 컴포넌트
├─ hooks/
└─ AnnotationCanvas.tsx설계 원칙
- 관심사 분리: Engine(로직, Canvas 렌더링) ↔ React(UI DOM, 상태 연동) 완전 분리
- 확장성:
Renderer,HitTest,Controller구조로 분리되어 있어, Polygon과 같은 새로운 Annotation 타입을 손쉽게 플러그인처럼 추가 가능합니다. - 사용자 경험(UX) 중심:
- 툴 오버 액션과 클릭/드래그 충돌을 방지하는 스마트 HitTest 우선순위
- 1초 Hover Timer 등 인간 중심적인 편집 인터랙션 제공
- 성능 최적화: 레이어별(base image / annotation) 독립된 Canvas를 사용하여 불필요한 이미지 렌더링(Redraw) 비용 원천 차단
