import React, {useRef, useEffect, useState, CSSProperties} from 'react'; interface Keypoint { position: { x: number; y: number }; part: string; score: number; } interface Pose { score: number; keypoints: Keypoint[]; } interface PoseEditorProps { imageSrc: string; poseData: Pose; onPoseChange?: (updatedPose: Pose) => void; width?: number height?: number styles?: CSSProperties } const PoseEditor = ({ imageSrc, poseData, onPoseChange, width = 640, height = 480, styles }: PoseEditorProps) => { const canvasRef = useRef(null); const [pose, setPose] = useState(poseData); const [dragging, setDragging] = useState<{ isDragging: boolean; keypointIndex: number | null; }>({ isDragging: false, keypointIndex: null, }); // 관절 그리기 함수 const drawKeypoints = (keypoints: Keypoint[], ctx: CanvasRenderingContext2D) => { keypoints.forEach(keypoint => { const { x, y } = keypoint.position; ctx.beginPath(); ctx.arc(x, y, 5, 0, 2 * Math.PI); ctx.fillStyle = 'red'; ctx.fill(); }); }; // 뼈대 그리기 함수 const drawSkeleton = (keypoints: Keypoint[], ctx: CanvasRenderingContext2D) => { const adjacentKeyPoints = [ ['nose', 'leftEye'], ['nose', 'rightEye'], ['leftEye', 'leftEar'], ['rightEye', 'rightEar'], ['nose', 'neck'], // 코에서 목으로 연결 ['neck', 'leftShoulder'], // 목에서 왼쪽 어깨로 연결 ['neck', 'rightShoulder'], // 목에서 오른쪽 어깨로 연결 ['leftShoulder', 'rightShoulder'], // 어깨끼리 연결 ['leftShoulder', 'leftElbow'], // 왼쪽 팔 연결 ['leftElbow', 'leftWrist'], // 왼쪽 팔꿈치에서 손목 연결 ['rightShoulder', 'rightElbow'], // 오른쪽 팔 연결 ['rightElbow', 'rightWrist'], // 오른쪽 팔꿈치에서 손목 연결 ['torso', 'leftHip'], // 몸통 중심에서 왼쪽 힙으로 연결 ['torso', 'rightHip'], // 몸통 중심에서 오른쪽 힙으로 연결 ['leftHip', 'rightHip'], // 양쪽 힙을 연결 ['leftHip', 'leftKnee'], // 왼쪽 다리 연결 ['leftKnee', 'leftAnkle'], // 왼쪽 무릎에서 발목 연결 ['rightHip', 'rightKnee'], // 오른쪽 다리 연결 ['rightKnee', 'rightAnkle'], // 오른쪽 무릎에서 발목 연결 ]; adjacentKeyPoints.forEach(([partA, partB]) => { const keypointA = keypoints.find(kp => kp.part === partA); const keypointB = keypoints.find(kp => kp.part === partB); if (keypointA && keypointB) { ctx.beginPath(); ctx.moveTo(keypointA.position.x, keypointA.position.y); ctx.lineTo(keypointB.position.x, keypointB.position.y); ctx.strokeStyle = 'blue'; ctx.lineWidth = 2; ctx.stroke(); } }); }; // 마우스 좌표 계산 함수 const getMousePos = (canvas: HTMLCanvasElement, evt: MouseEvent | TouchEvent) => { const rect = canvas.getBoundingClientRect(); let clientX, clientY; if (evt instanceof MouseEvent) { clientX = evt.clientX; clientY = evt.clientY; } else { clientX = evt.touches[0].clientX; clientY = evt.touches[0].clientY; } return { x: clientX - rect.left, y: clientY - rect.top, }; }; // 이벤트 핸들러 const handleMouseDown = (e: any) => { e.preventDefault(); if (!canvasRef.current) return; const mousePos = getMousePos(canvasRef.current, e); const radius = 5; const keypointIndex = pose.keypoints.findIndex(keypoint => { const dx = keypoint.position.x - mousePos.x; const dy = keypoint.position.y - mousePos.y; return dx * dx + dy * dy < radius * radius; }); if (keypointIndex !== -1) { setDragging({ isDragging: true, keypointIndex }); } }; const handleMouseMove = (e: any) => { e.preventDefault(); if (!canvasRef.current || !dragging.isDragging) return; const mousePos = getMousePos(canvasRef.current, e); const updatedPose = { ...pose }; updatedPose.keypoints[dragging.keypointIndex!].position = { x: mousePos.x, y: mousePos.y, }; setPose(updatedPose); // 포즈 변경 콜백 호출 if (onPoseChange) { onPoseChange(updatedPose); } }; const handleMouseUp = (e: any) => { e.preventDefault(); if (dragging.isDragging) { setDragging({ isDragging: false, keypointIndex: null }); } }; // 이미지 로드 및 그리기 useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const context = canvas?.getContext('2d'); if (!context) return; const image = new Image(); image.src = imageSrc; image.onload = () => { context.clearRect(0, 0, canvas.width, canvas.height); context.drawImage(image, 0, 0, canvas.width, canvas.height); drawKeypoints(pose.keypoints, context); drawSkeleton(pose.keypoints, context); }; }, [pose, imageSrc]); // 마우스 이벤트 핸들러 등록 useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; canvas.addEventListener('mousedown', handleMouseDown); canvas.addEventListener('mousemove', handleMouseMove); canvas.addEventListener('mouseup', handleMouseUp); return () => { canvas.removeEventListener('mousedown', handleMouseDown); canvas.removeEventListener('mousemove', handleMouseMove); canvas.removeEventListener('mouseup', handleMouseUp); }; }, [pose, dragging]); return ( ); }; export default PoseEditor;