Feat: 캔버스 폴리곤 그리기 기능 구현
This commit is contained in:
parent
e7a70d303c
commit
28a7e91390
@ -1,7 +1,7 @@
|
|||||||
import useCanvasStore from '@/stores/useCanvasStore';
|
import useCanvasStore from '@/stores/useCanvasStore';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { Image, Layer, Rect, Stage } from 'react-konva';
|
import { Circle, Image, Layer, Line, Rect, Stage } from 'react-konva';
|
||||||
import useImage from 'use-image';
|
import useImage from 'use-image';
|
||||||
import { Label } from '@/types';
|
import { Label } from '@/types';
|
||||||
import LabelRect from './LabelRect';
|
import LabelRect from './LabelRect';
|
||||||
@ -47,7 +47,9 @@ export default function ImageCanvas() {
|
|||||||
const [selectedId, setSelectedId] = useState<number | null>(null);
|
const [selectedId, setSelectedId] = useState<number | null>(null);
|
||||||
const [image, imageStatus] = useImage(imageUrl);
|
const [image, imageStatus] = useImage(imageUrl);
|
||||||
const [rectPoints, setRectPoints] = useState<[number, number][]>([]);
|
const [rectPoints, setRectPoints] = useState<[number, number][]>([]);
|
||||||
|
const [polygonPoints, setPolygonPoints] = useState<[number, number][]>([]);
|
||||||
const drawState = useCanvasStore((state) => state.drawState);
|
const drawState = useCanvasStore((state) => state.drawState);
|
||||||
|
const setDrawState = useCanvasStore((state) => state.setDrawState);
|
||||||
const addLabel = useCanvasStore((state) => state.addLabel);
|
const addLabel = useCanvasStore((state) => state.addLabel);
|
||||||
const startDrawRect = () => {
|
const startDrawRect = () => {
|
||||||
const { x, y } = stageRef.current!.getRelativePointerPosition()!;
|
const { x, y } = stageRef.current!.getRelativePointerPosition()!;
|
||||||
@ -56,6 +58,59 @@ export default function ImageCanvas() {
|
|||||||
[x, y],
|
[x, y],
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
const addPointToPolygon = () => {
|
||||||
|
const { x, y } = stageRef.current!.getRelativePointerPosition()!;
|
||||||
|
if (polygonPoints.length === 0) {
|
||||||
|
setPolygonPoints([
|
||||||
|
[x, y],
|
||||||
|
[x, y],
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const diff = Math.max(Math.abs(x - polygonPoints[0][0]), Math.abs(y - polygonPoints[0][1]));
|
||||||
|
|
||||||
|
if (diff === 0) return;
|
||||||
|
|
||||||
|
const scale = stageRef.current!.getAbsoluteScale().x;
|
||||||
|
const clickedFirstPoint = polygonPoints.length > 1 && diff * scale < 5;
|
||||||
|
|
||||||
|
if (clickedFirstPoint) {
|
||||||
|
endDrawPolygon();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setPolygonPoints([...polygonPoints, [x, y]]);
|
||||||
|
};
|
||||||
|
const removeLastPointOfPolygon = (e: MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (polygonPoints.length === 0) return;
|
||||||
|
setPolygonPoints(polygonPoints.slice(0, -1));
|
||||||
|
};
|
||||||
|
const moveLastPointOfPolygon = () => {
|
||||||
|
if (polygonPoints.length < 2) return;
|
||||||
|
const { x, y } = stageRef.current!.getRelativePointerPosition()!;
|
||||||
|
setPolygonPoints([...polygonPoints.slice(0, -1), [x, y]]);
|
||||||
|
};
|
||||||
|
const endDrawPolygon = () => {
|
||||||
|
if (drawState !== 'pen' || polygonPoints.length === 0) return;
|
||||||
|
setDrawState('pointer');
|
||||||
|
setPolygonPoints([]);
|
||||||
|
if (polygonPoints.length < 4) return;
|
||||||
|
|
||||||
|
const color = Math.floor(Math.random() * 65535)
|
||||||
|
.toString(16)
|
||||||
|
.padStart(6, '0');
|
||||||
|
const id = labels.length;
|
||||||
|
addLabel({
|
||||||
|
id: id,
|
||||||
|
name: 'label',
|
||||||
|
type: 'polygon',
|
||||||
|
color: `#${color}`,
|
||||||
|
coordinates: polygonPoints.slice(0, -1),
|
||||||
|
});
|
||||||
|
setDrawState('pointer');
|
||||||
|
setSelectedId(id);
|
||||||
|
};
|
||||||
const updateDrawingRect = () => {
|
const updateDrawingRect = () => {
|
||||||
if (rectPoints.length === 0) return;
|
if (rectPoints.length === 0) return;
|
||||||
|
|
||||||
@ -71,23 +126,32 @@ export default function ImageCanvas() {
|
|||||||
setRectPoints([]);
|
setRectPoints([]);
|
||||||
const color = Math.floor(Math.random() * 65535)
|
const color = Math.floor(Math.random() * 65535)
|
||||||
.toString(16)
|
.toString(16)
|
||||||
.padStart(4, '0');
|
.padStart(6, '0');
|
||||||
|
const id = labels.length;
|
||||||
addLabel({
|
addLabel({
|
||||||
id: labels.length,
|
id: id,
|
||||||
name: 'label',
|
name: 'label',
|
||||||
type: 'rect',
|
type: 'rect',
|
||||||
color: `#${color}ff`,
|
color: `#${color}`,
|
||||||
coordinates: rectPoints,
|
coordinates: rectPoints,
|
||||||
});
|
});
|
||||||
|
setDrawState('pointer');
|
||||||
|
setSelectedId(id);
|
||||||
};
|
};
|
||||||
const handleClick = (e: Konva.KonvaEventObject<MouseEvent | TouchEvent>) => {
|
const handleClick = (e: Konva.KonvaEventObject<MouseEvent | TouchEvent>) => {
|
||||||
const isLeftMouseClicked = e.evt.type === 'mousedown' && (e.evt as MouseEvent).button === 0;
|
e.evt.preventDefault();
|
||||||
|
e.evt.stopPropagation();
|
||||||
|
const isLeftClicked = e.evt.type === 'mousedown' && (e.evt as MouseEvent).button === 0;
|
||||||
|
const isRightClicked = e.evt.type === 'mousedown' && (e.evt as MouseEvent).button === 2;
|
||||||
|
|
||||||
if (drawState !== 'pointer' && isLeftMouseClicked) {
|
if (drawState !== 'pointer' && (isLeftClicked || isRightClicked)) {
|
||||||
stageRef.current?.stopDrag();
|
stageRef.current?.stopDrag();
|
||||||
if (drawState === 'rect') {
|
if (drawState === 'rect') {
|
||||||
startDrawRect();
|
startDrawRect();
|
||||||
}
|
}
|
||||||
|
if (drawState === 'pen') {
|
||||||
|
isRightClicked ? removeLastPointOfPolygon(e.evt as MouseEvent) : addPointToPolygon();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.target === e.target.getStage() || e.target.getClassName() === 'Image') {
|
if (e.target === e.target.getStage() || e.target.getClassName() === 'Image') {
|
||||||
@ -98,6 +162,9 @@ export default function ImageCanvas() {
|
|||||||
if (drawState === 'rect' && rectPoints.length) {
|
if (drawState === 'rect' && rectPoints.length) {
|
||||||
updateDrawingRect();
|
updateDrawingRect();
|
||||||
}
|
}
|
||||||
|
if (drawState === 'pen' && polygonPoints.length) {
|
||||||
|
moveLastPointOfPolygon();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const handleZoom = (e: Konva.KonvaEventObject<WheelEvent>) => {
|
const handleZoom = (e: Konva.KonvaEventObject<WheelEvent>) => {
|
||||||
const scaleBy = 1.05;
|
const scaleBy = 1.05;
|
||||||
@ -172,6 +239,7 @@ export default function ImageCanvas() {
|
|||||||
onMouseUp={endDrawRect}
|
onMouseUp={endDrawRect}
|
||||||
onTouchEnd={endDrawRect}
|
onTouchEnd={endDrawRect}
|
||||||
scale={getScale()}
|
scale={getScale()}
|
||||||
|
onContextMenu={(e) => e.evt.preventDefault()}
|
||||||
>
|
>
|
||||||
<Layer>
|
<Layer>
|
||||||
<Image image={image} />
|
<Image image={image} />
|
||||||
@ -203,15 +271,47 @@ export default function ImageCanvas() {
|
|||||||
y={rectPoints[0][1]}
|
y={rectPoints[0][1]}
|
||||||
width={rectPoints[1][0] - rectPoints[0][0]}
|
width={rectPoints[1][0] - rectPoints[0][0]}
|
||||||
height={rectPoints[1][1] - rectPoints[0][1]}
|
height={rectPoints[1][1] - rectPoints[0][1]}
|
||||||
stroke={'#ff0000'}
|
stroke={'#00a1ff'}
|
||||||
strokeWidth={1}
|
strokeWidth={1}
|
||||||
strokeScaleEnabled={false}
|
strokeScaleEnabled={false}
|
||||||
fillAfterStrokeEnabled={false}
|
fillAfterStrokeEnabled={false}
|
||||||
fill="#ff000033"
|
fill="#00a1ff33"
|
||||||
shadowForStrokeEnabled={false}
|
shadowForStrokeEnabled={false}
|
||||||
listening={false}
|
listening={false}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
{polygonPoints.length ? (
|
||||||
|
<>
|
||||||
|
<Line
|
||||||
|
points={polygonPoints.flat()}
|
||||||
|
stroke={'#00a1ff'}
|
||||||
|
strokeWidth={1}
|
||||||
|
strokeScaleEnabled={false}
|
||||||
|
fill="#00a1ff33"
|
||||||
|
closed
|
||||||
|
listening={false}
|
||||||
|
/>
|
||||||
|
{polygonPoints.map((point, index) => (
|
||||||
|
<Circle
|
||||||
|
key={index}
|
||||||
|
x={point[0]}
|
||||||
|
y={point[1]}
|
||||||
|
radius={5}
|
||||||
|
stroke="#00a1ff"
|
||||||
|
strokeWidth={1}
|
||||||
|
fill="white"
|
||||||
|
strokeScaleEnabled={false}
|
||||||
|
listening={false}
|
||||||
|
scale={{
|
||||||
|
x: 1 / stageRef.current!.getAbsoluteScale().x,
|
||||||
|
y: 1 / stageRef.current!.getAbsoluteScale().y,
|
||||||
|
}}
|
||||||
|
perfectDrawEnabled={false}
|
||||||
|
shadowForStrokeEnabled={false}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</Layer>
|
</Layer>
|
||||||
|
|
||||||
<Layer ref={dragLayerRef} />
|
<Layer ref={dragLayerRef} />
|
||||||
|
Loading…
Reference in New Issue
Block a user