"use client"; import React, { useState, useRef, useCallback } from "react"; import { Button } from "@/components/ui/Button"; import { Card } from "@/components/ui/Card"; import { Modal } from "@/components/ui/Modal"; interface ImageEditorProps { isOpen: boolean; onClose: () => void; imageUrl: string; onSave: (editedImageUrl: string) => void; className?: string; } interface CropArea { x: number; y: number; width: number; height: number; } export function ImageEditor({ isOpen, onClose, imageUrl, onSave, className, }: ImageEditorProps) { const canvasRef = useRef(null); const [image, setImage] = useState(null); const [rotation, setRotation] = useState(0); const [flipHorizontal, setFlipHorizontal] = useState(false); const [flipVertical, setFlipVertical] = useState(false); const [cropMode, setCropMode] = useState(false); const [cropArea, setCropArea] = useState({ x: 0, y: 0, width: 0, height: 0, }); const [isDragging, setIsDragging] = useState(false); const [dragStart, setDragStart] = useState({ x: 0, y: 0 }); // 이미지 로드 React.useEffect(() => { if (imageUrl && isOpen) { const img = new Image(); img.crossOrigin = "anonymous"; img.onload = () => { setImage(img); drawCanvas(img); }; img.src = imageUrl; } }, [imageUrl, isOpen]); // 캔버스 그리기 const drawCanvas = useCallback( (img?: HTMLImageElement, applyTransforms = true) => { const canvas = canvasRef.current; if (!canvas || !img) return; const ctx = canvas.getContext("2d"); if (!ctx) return; // 캔버스 크기 설정 const maxWidth = 600; const maxHeight = 400; let { width, height } = img; // 비율 유지하며 크기 조정 if (width > maxWidth) { height = (height * maxWidth) / width; width = maxWidth; } if (height > maxHeight) { width = (width * maxHeight) / height; height = maxHeight; } canvas.width = width; canvas.height = height; // 캔버스 초기화 ctx.clearRect(0, 0, width, height); if (applyTransforms) { // 회전 및 뒤집기 변환 적용 ctx.save(); ctx.translate(width / 2, height / 2); if (rotation !== 0) { ctx.rotate((rotation * Math.PI) / 180); } ctx.scale(flipHorizontal ? -1 : 1, flipVertical ? -1 : 1); ctx.drawImage(img, -width / 2, -height / 2, width, height); ctx.restore(); } else { ctx.drawImage(img, 0, 0, width, height); } // 크롭 영역 그리기 if (cropMode && cropArea.width > 0 && cropArea.height > 0) { ctx.strokeStyle = "#3b82f6"; ctx.lineWidth = 2; ctx.setLineDash([5, 5]); ctx.strokeRect(cropArea.x, cropArea.y, cropArea.width, cropArea.height); // 어두운 오버레이 ctx.fillStyle = "rgba(0, 0, 0, 0.5)"; ctx.fillRect(0, 0, width, height); ctx.clearRect(cropArea.x, cropArea.y, cropArea.width, cropArea.height); // 크롭 영역 다시 그리기 ctx.globalCompositeOperation = "source-over"; ctx.drawImage( img, cropArea.x, cropArea.y, cropArea.width, cropArea.height, cropArea.x, cropArea.y, cropArea.width, cropArea.height ); } }, [rotation, flipHorizontal, flipVertical, cropMode, cropArea] ); // 변환 적용 시 캔버스 업데이트 React.useEffect(() => { if (image) { drawCanvas(image); } }, [image, drawCanvas]); // 회전 const handleRotate = (angle: number) => { setRotation((prev) => (prev + angle) % 360); }; // 뒤집기 const handleFlip = (direction: "horizontal" | "vertical") => { if (direction === "horizontal") { setFlipHorizontal((prev) => !prev); } else { setFlipVertical((prev) => !prev); } }; // 크롭 모드 토글 const toggleCropMode = () => { setCropMode((prev) => !prev); if (cropMode) { setCropArea({ x: 0, y: 0, width: 0, height: 0 }); } }; // 마우스 다운 (크롭 시작) const handleMouseDown = (e: React.MouseEvent) => { if (!cropMode) return; const canvas = canvasRef.current; if (!canvas) return; const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; setIsDragging(true); setDragStart({ x, y }); setCropArea({ x, y, width: 0, height: 0 }); }; // 마우스 이동 (크롭 영역 조정) const handleMouseMove = (e: React.MouseEvent) => { if (!cropMode || !isDragging) return; const canvas = canvasRef.current; if (!canvas) return; const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const width = x - dragStart.x; const height = y - dragStart.y; setCropArea({ x: width > 0 ? dragStart.x : x, y: height > 0 ? dragStart.y : y, width: Math.abs(width), height: Math.abs(height), }); }; // 마우스 업 (크롭 완료) const handleMouseUp = () => { setIsDragging(false); }; // 크롭 적용 const applyCrop = () => { const canvas = canvasRef.current; if (!canvas || !image || cropArea.width === 0 || cropArea.height === 0) return; const croppedCanvas = document.createElement("canvas"); const ctx = croppedCanvas.getContext("2d"); if (!ctx) return; croppedCanvas.width = cropArea.width; croppedCanvas.height = cropArea.height; // 원본 이미지에서 크롭 영역 복사 ctx.drawImage( canvas, cropArea.x, cropArea.y, cropArea.width, cropArea.height, 0, 0, cropArea.width, cropArea.height ); return croppedCanvas.toDataURL("image/png"); }; // 저장 const handleSave = () => { const canvas = canvasRef.current; if (!canvas) return; let dataUrl; if (cropMode && cropArea.width > 0 && cropArea.height > 0) { dataUrl = applyCrop(); } else { dataUrl = canvas.toDataURL("image/png"); } if (dataUrl) { onSave(dataUrl); handleReset(); onClose(); } }; // 리셋 const handleReset = () => { setRotation(0); setFlipHorizontal(false); setFlipVertical(false); setCropMode(false); setCropArea({ x: 0, y: 0, width: 0, height: 0 }); if (image) { drawCanvas(image); } }; return (

이미지 편집

{/* 도구 모음 */}
{/* 캔버스 영역 */}
{cropMode && (
마우스를 드래그하여 자를 영역을 선택하세요
)}
{/* 하단 버튼 */}
); }