using System; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; namespace AGVMapEditor.Controls { /// /// 이미지 편집용 사용자 정의 캔버스 컨트롤 /// 이미지 중앙 정렬, 크기 조정 핸들, 브러시 그리기 기능 제공 /// public class ImageEditorCanvas : UserControl { private Bitmap _editingImage; private Graphics _imageGraphics; private Rectangle _imageRect = Rectangle.Empty; private float _imageDisplayWidth = 0; private float _imageDisplayHeight = 0; // 브러시 그리기 private bool _isDrawing = false; private Point _lastDrawPoint = Point.Empty; private Color _drawColor = Color.Black; private int _brushSize = 3; private bool _brushModeEnabled = false; // 크기 조정 private bool _isResizing = false; private ResizeHandle _activeHandle = ResizeHandle.None; private Point _resizeStartPoint = Point.Empty; private float _resizeStartWidth = 0; private float _resizeStartHeight = 0; private const int HANDLE_SIZE = 8; private enum ResizeHandle { None, TopLeft, Top, TopRight, Right, BottomRight, Bottom, BottomLeft, Left } public ImageEditorCanvas() { this.DoubleBuffered = true; this.BackColor = Color.White; this.AutoScroll = true; } #region Properties public Bitmap EditingImage { get => _editingImage; set { _editingImage = value; if (_editingImage != null) { _imageGraphics?.Dispose(); _imageGraphics = Graphics.FromImage(_editingImage); _imageDisplayWidth = _editingImage.Width; _imageDisplayHeight = _editingImage.Height; UpdateImageRect(); } Invalidate(); } } public Color DrawColor { get => _drawColor; set => _drawColor = value; } public int BrushSize { get => _brushSize; set => _brushSize = value; } public bool BrushModeEnabled { get => _brushModeEnabled; set => _brushModeEnabled = value; } public Size ImageDisplaySize => new Size((int)_imageDisplayWidth, (int)_imageDisplayHeight); #endregion #region Paint protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); if (_editingImage == null) { e.Graphics.Clear(BackColor); return; } // 배경 채우기 e.Graphics.Clear(BackColor); // 이미지 영역 업데이트 UpdateImageRect(); // 이미지 그리기 (고품질) e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; e.Graphics.DrawImage(_editingImage, _imageRect); // 크기 조정 핸들 그리기 DrawResizeHandles(e.Graphics); } private void UpdateImageRect() { if (_editingImage == null || Width == 0 || Height == 0) { _imageRect = Rectangle.Empty; return; } // 이미지를 중앙 정렬 float x = (Width - _imageDisplayWidth) / 2f; float y = (Height - _imageDisplayHeight) / 2f; // 음수 방지 if (x < 0) x = 0; if (y < 0) y = 0; _imageRect = new Rectangle((int)x, (int)y, (int)_imageDisplayWidth, (int)_imageDisplayHeight); } private void DrawResizeHandles(Graphics g) { if (_imageRect.IsEmpty) return; var handles = GetResizeHandles(); foreach (var handle in handles) { g.FillRectangle(Brushes.Blue, handle); g.DrawRectangle(Pens.White, handle); } } private Rectangle[] GetResizeHandles() { int x = _imageRect.X; int y = _imageRect.Y; int w = _imageRect.Width; int h = _imageRect.Height; int hs = HANDLE_SIZE; return new Rectangle[] { new Rectangle(x - hs/2, y - hs/2, hs, hs), // TopLeft new Rectangle(x + w/2 - hs/2, y - hs/2, hs, hs), // Top new Rectangle(x + w - hs/2, y - hs/2, hs, hs), // TopRight new Rectangle(x + w - hs/2, y + h/2 - hs/2, hs, hs), // Right new Rectangle(x + w - hs/2, y + h - hs/2, hs, hs), // BottomRight new Rectangle(x + w/2 - hs/2, y + h - hs/2, hs, hs), // Bottom new Rectangle(x - hs/2, y + h - hs/2, hs, hs), // BottomLeft new Rectangle(x - hs/2, y + h/2 - hs/2, hs, hs) // Left }; } #endregion #region Mouse Events protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); if (_editingImage == null || e.Button != MouseButtons.Left) return; // 크기 조정 핸들 확인 _activeHandle = GetHandleAtPoint(e.Location); if (_activeHandle != ResizeHandle.None) { _isResizing = true; _resizeStartPoint = e.Location; _resizeStartWidth = _imageDisplayWidth; _resizeStartHeight = _imageDisplayHeight; return; } // 브러시 모드: 그리기 if (_brushModeEnabled && _imageRect.Contains(e.Location)) { _isDrawing = true; _lastDrawPoint = ImagePointFromScreen(e.Location); } } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (_editingImage == null) return; // 크기 조정 중 if (_isResizing && _activeHandle != ResizeHandle.None) { ResizeImageDisplay(e.Location); return; } // 크기 조정 핸들 위에 마우스가 있으면 커서 변경 var handle = GetHandleAtPoint(e.Location); if (handle != ResizeHandle.None) { Cursor = GetCursorForHandle(handle); return; } else { Cursor = Cursors.Default; } // 브러시 모드: 그리기 if (_isDrawing && _lastDrawPoint != Point.Empty && _brushModeEnabled && _imageRect.Contains(e.Location)) { Point currentImagePoint = ImagePointFromScreen(e.Location); _imageGraphics.DrawLine(new Pen(_drawColor, _brushSize), _lastDrawPoint, currentImagePoint); _lastDrawPoint = currentImagePoint; Invalidate(); } } protected override void OnMouseUp(MouseEventArgs e) { base.OnMouseUp(e); if (_isResizing) { _isResizing = false; _activeHandle = ResizeHandle.None; } if (_isDrawing) { _isDrawing = false; _lastDrawPoint = Point.Empty; } } protected override void OnResize(EventArgs e) { base.OnResize(e); UpdateImageRect(); Invalidate(); } #endregion #region Helper Methods /// /// 화면 좌표를 이미지 좌표로 변환 /// private Point ImagePointFromScreen(Point screenPoint) { if (_imageRect.IsEmpty || _editingImage == null) return Point.Empty; // 화면 좌표를 이미지 비율로 변환 float scaleX = (float)_editingImage.Width / _imageRect.Width; float scaleY = (float)_editingImage.Height / _imageRect.Height; int imageX = (int)((screenPoint.X - _imageRect.X) * scaleX); int imageY = (int)((screenPoint.Y - _imageRect.Y) * scaleY); return new Point(imageX, imageY); } private ResizeHandle GetHandleAtPoint(Point pt) { var handles = GetResizeHandles(); var handleTypes = new[] { ResizeHandle.TopLeft, ResizeHandle.Top, ResizeHandle.TopRight, ResizeHandle.Right, ResizeHandle.BottomRight, ResizeHandle.Bottom, ResizeHandle.BottomLeft, ResizeHandle.Left }; for (int i = 0; i < handles.Length; i++) { if (handles[i].Contains(pt)) return handleTypes[i]; } return ResizeHandle.None; } private Cursor GetCursorForHandle(ResizeHandle handle) { switch (handle) { case ResizeHandle.TopLeft: case ResizeHandle.BottomRight: return Cursors.SizeNWSE; case ResizeHandle.TopRight: case ResizeHandle.BottomLeft: return Cursors.SizeNESW; case ResizeHandle.Top: case ResizeHandle.Bottom: return Cursors.SizeNS; case ResizeHandle.Left: case ResizeHandle.Right: return Cursors.SizeWE; default: return Cursors.Default; } } private void ResizeImageDisplay(Point currentPoint) { int deltaX = currentPoint.X - _resizeStartPoint.X; int deltaY = currentPoint.Y - _resizeStartPoint.Y; float newWidth = _resizeStartWidth; float newHeight = _resizeStartHeight; switch (_activeHandle) { case ResizeHandle.TopLeft: newWidth -= deltaX; newHeight -= deltaY; break; case ResizeHandle.Top: newHeight -= deltaY; break; case ResizeHandle.TopRight: newWidth += deltaX; newHeight -= deltaY; break; case ResizeHandle.Right: newWidth += deltaX; break; case ResizeHandle.BottomRight: newWidth += deltaX; newHeight += deltaY; break; case ResizeHandle.Bottom: newHeight += deltaY; break; case ResizeHandle.BottomLeft: newWidth -= deltaX; newHeight += deltaY; break; case ResizeHandle.Left: newWidth -= deltaX; break; } // 최소 크기 제한 if (newWidth < 50) newWidth = 50; if (newHeight < 50) newHeight = 50; _imageDisplayWidth = newWidth; _imageDisplayHeight = newHeight; UpdateImageRect(); Invalidate(); } /// /// 표시 크기로 실제 이미지 리사이즈 /// public Bitmap GetResizedImage() { if (_editingImage == null) return null; int targetWidth = (int)_imageDisplayWidth; int targetHeight = (int)_imageDisplayHeight; // 크기가 같으면 원본 반환 if (targetWidth == _editingImage.Width && targetHeight == _editingImage.Height) return new Bitmap(_editingImage); // 리사이즈 var resized = new Bitmap(targetWidth, targetHeight); using (var g = Graphics.FromImage(resized)) { g.CompositingQuality = CompositingQuality.HighQuality; g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.SmoothingMode = SmoothingMode.HighQuality; g.DrawImage(_editingImage, 0, 0, targetWidth, targetHeight); } return resized; } #endregion protected override void Dispose(bool disposing) { if (disposing) { _imageGraphics?.Dispose(); } base.Dispose(disposing); } } }