Files
ENIG/Cs_HMI/AGVLogic/AGVMapEditor/Controls/ImageEditorCanvas.cs
backuppc 3f6db7113f fix: 맵 에디터 연결 버그 수정 및 기능 개선
주요 변경사항:
- ConnectedMapNodes 속성 추가로 런타임 객체 참조 지원
- 이미지 에디터 UI 개선 (ImageEditorCanvas 추가)
- 연결 생성 버그 수정: 양방향 연결 생성
- 연결 삭제 버그 수정: 양방향 모두 제거
- CleanupDuplicateConnections 비활성화 (단방향 변환 버그)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 09:34:10 +09:00

414 lines
13 KiB
C#

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace AGVMapEditor.Controls
{
/// <summary>
/// 이미지 편집용 사용자 정의 캔버스 컨트롤
/// 이미지 중앙 정렬, 크기 조정 핸들, 브러시 그리기 기능 제공
/// </summary>
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
/// <summary>
/// 화면 좌표를 이미지 좌표로 변환
/// </summary>
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();
}
/// <summary>
/// 표시 크기로 실제 이미지 리사이즈
/// </summary>
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);
}
}
}