2025-03-16 17:32:09 +08:00

2020 lines
61 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using CanFly.Canvas.Helper;
using CanFly.Canvas.Model;
using CanFly.Canvas.Shape;
using LabelSharp.Config;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using MethodInvoker = System.Windows.Forms.MethodInvoker;
namespace CanFly.Canvas.UI
{
public partial class FlyCanvas : UserControl
{
public event Action OnMenuItemCopyToHere;
public event Action OnMenuItemMoveToHere;
public event Action<int, Point> zoomRequest;
public event Action<float> ZoomRequestF;
public event Action<int, int> scrollRequest;
public event Action newShape;
public event Action<List<FlyShape>> selectionChanged;
public event Action ShapeMoved;
public event Action<List<FlyShape>> OnShapeMoving;
public event Action<bool> DrawingPolygon;
public event Action<bool> vertexSelected;
public event Action<PointF> mouseMoved;
public event Action<FlyShape> OnShapeUpdateEvent;
public ShapeTypeEnum _createMode = ShapeTypeEnum.Polygon;
public bool _fill_drawing = false;
private float epsilon = 8.0f;
private DoubleClickActionEnum double_click = DoubleClickActionEnum.Close;
private int num_backups = 10;
private bool _isEditMode = true;
public List<FlyShape> Shapes { get; set; } = new List<FlyShape>();
/// <summary>
/// 外部绘制的
/// </summary>
public List<FlyShape> OutsideShapes { get; set; } = new List<FlyShape>();
public List<List<FlyShape>> shapesBackups = new();
private FlyShape? current = null;
public List<FlyShape> selectedShapes = new List<FlyShape>();
private List<FlyShape> selectedShapesCopy = new List<FlyShape>();
private FlyShape line = new FlyShape();
private PointF prevPoint;
private PointF prevMovePoint;
private PointF[] offsets = new PointF[2] { new PointF(), new PointF() };
private float MOVE_SPEED = 5.0f;
/// <summary>
/// 是否允许多选
/// </summary>
public bool AllowMultiSelect { get; set; } = false;
public int _width { get; private set; }
public int _height { get; private set; }
//public List<ContextMenuStrip> MenuStripsWithoutSelection = new List<ContextMenuStrip>();
//public List<ContextMenuStrip> MenuStripsWithSelection = new List<ContextMenuStrip>();
/// <summary>
/// 缩放比例该参数必须大于0
/// </summary>
private float _scale = 1.0f;
public new float Scale
{
get { return _scale; }
set
{
if (value <= 0.0001)
{
return;
}
_scale = value;
ZoomRequestF?.Invoke(_scale);
}
}
public Bitmap? pixmap;// = new Bitmap();
private Dictionary<Shape.FlyShape, bool> visible = new Dictionary<Shape.FlyShape, bool>();
private bool _hideBackround = false;
private bool hideBackround = false;
#region
private Shape.FlyShape? hShape;
private Shape.FlyShape? prevhShape;
private int hVertex = -1;
private int prevhVertex = -1;
private int hEdge;
private int prevhEdge;
#endregion
private bool movingShape = false;
private bool snapping = true;
private bool hShapeIsSelected = false;
private Graphics _painter;
private Cursor _cursor = CustomCursors.CURSOR_DEFAULT;
private Cursor _preCursor = CustomCursors.CURSOR_DEFAULT;
/// <summary>
/// 点击的区域
/// </summary>
internal enum ClickArea
{
/// <summary>
/// 未知区域
/// </summary>
AREA_UNKNOW,
/// <summary>
/// 图片区域
/// </summary>
AREA_IMG,
/// <summary>
/// 缺陷元素区域
/// </summary>
AREA_DEFECT,
}
private ClickArea _clickArea = ClickArea.AREA_UNKNOW;
/// <summary>
/// 矩形框
/// </summary>
private Rectangle _rcImg = new Rectangle(0, 0, 0, 0);
/// <summary>
/// 变换矩阵
/// </summary>
private Matrix _matrix = new Matrix();
public Matrix Matrix => _matrix.Clone();
//private Bitmap image;
//private List<Rectangle> rectangles = new List<Rectangle>();
//private Rectangle currentRectangle;
//private bool drawing = false;
public FlyCanvas()
{
InitializeComponent();
// this.KeyDown += FlyCanvas_KeyDown;
// this.KeyPress += FlyCanvas_KeyPress;
this.DoubleBuffered = true;
SetStyle(ControlStyles.AllPaintingInWmPaint//控件忽略窗口消息 WM_ERASEBKGND 以减少闪烁
| ControlStyles.UserPaint//由控件而不是由操作系统来绘制控件自身,即自定义绘制
| ControlStyles.OptimizedDoubleBuffer//控件将首先绘制到缓冲区而不是直接绘制到屏幕,这可以减少闪烁
| ControlStyles.ResizeRedraw, true);//控件会在调整大小时进行重绘
//InitTask();
}
private void Init()
{
_clickArea = ClickArea.AREA_UNKNOW;//枚举
_rcImg = new Rectangle(0, 0, 0, 0);
_matrix.Reset();//重置为单位矩阵
// strImgPath = string.Empty;
pixmap = null;
}
public bool FillDrawing
{
get { return _fill_drawing; }
set { _fill_drawing = value; }
}
/// <summary>
/// 图形类型
/// </summary>
public ShapeTypeEnum CreateMode
{
get { return _createMode; }
set { _createMode = value; }
}
public void StoreShapes()
{
var shapesBackup = new List<Shape.FlyShape>();
foreach (var shape in Shapes)
{
shapesBackup.Add(shape.Copy());
}
// 限制备份数量
if (shapesBackups.Count >= num_backups)
{
shapesBackups.RemoveRange(0, shapesBackups.Count - num_backups + 1); // 移除多余的备份
}
shapesBackups.Add(shapesBackup); // 添加当前备份
}
public bool IsShapeRestorable
{
get
{
if (shapesBackups.Count < 2)
{
return false;
}
return true;
}
}
public void RestoreShape()
{
if (!this.IsShapeRestorable)
{
return;
}
// 确保备份不为空
if (shapesBackups.Count == 0)
{
return; // 如果没有备份,直接返回
}
shapesBackups.RemoveAt(shapesBackups.Count - 1);
// 弹出最新的备份
List<Shape.FlyShape> shapesBackup = shapesBackups[^1]; // 获取最后的备份而不移除它
shapesBackups.RemoveAt(shapesBackups.Count - 1); // 移除最新的备份
// 恢复形状
this.Shapes = shapesBackup;
this.selectedShapes = new List<Shape.FlyShape>(); // 清空选中的形状
foreach (Shape.FlyShape shape in Shapes)
{
shape.Selected = false; // 取消所有形状的选中状态
}
Invalidate();
}
public bool IsVisible(FlyShape shape)
{
if (visible.Keys.Contains(shape))
{
return visible[shape];
}
return true;
}
public bool Drawing() => !_isEditMode;
public bool Editing() => _isEditMode;
private void UnHighlight()
{
if (hShape != null)
{
hShape.HighlightClear();
Invalidate();
}
prevhShape = hShape;
prevhVertex = hVertex;
prevhEdge = hEdge;
hShape = null;
hVertex = -1;
hEdge = -1;
}
public bool SelectedVertex() => hVertex >= 0;
public bool SelectedEdge() => hEdge >= 0;
private void DeSelectShape()
{
if (this.selectedShapes != null)
{
this.SetHiding(false);
{
this.selectedShapes.ForEach(shape => shape.Selected = false);
this.selectedShapes.Clear();
}
this.selectionChanged?.Invoke(new List<FlyShape>());
this.hShapeIsSelected = false;
Invalidate();
}
}
public List<FlyShape> DeleteSelected()
{
List<Shape.FlyShape> deleted_shapes = new List<Shape.FlyShape>();
if (this.selectedShapes != null)
{
foreach (var shape in this.selectedShapes)
{
this.Shapes.Remove(shape);
deleted_shapes.Add(shape);
}
this.StoreShapes();
this.selectedShapes = new List<Shape.FlyShape>();
Invalidate();
}
return deleted_shapes;
}
private void DeleteShape(Shape.FlyShape shape)
{
if (this.selectedShapes.Contains(shape))
{
this.selectedShapes.Remove(shape);
}
if (this.Shapes.Contains(shape))
{
this.Shapes.Remove(shape);
}
this.StoreShapes();
Invalidate();
}
public void LoadImage(string path)
{
//image = new Bitmap(path);
//this.Invalidate();
}
private PointF _panBasePoint = PointF.Empty;
private void Finalise()
{
this.current.Close();
this.Shapes.Add(this.current);
this.StoreShapes();
this.current = null;
this.SetHiding(false);
newShape?.Invoke();
Invalidate();
}
private void SetHiding(bool enable = true)
{
this._hideBackround = enable ? this.hideBackround : false;
}
private void FlyCanvas_MouseWheel(object? sender, MouseEventArgs e)
{
// base.OnMouseWheel(e);
//绑定滚轮键
if (_clickArea == ClickArea.AREA_UNKNOW || _clickArea == ClickArea.AREA_IMG)
{
var delta = e.Delta;
float scaleFactor = delta > 0 ? 1.1f : 0.9f;
Scale *= scaleFactor;
if (Scale >= 10)
{
return;
}
var p = e.Location.ToImageCoordinate(_matrix);
var mat = new Matrix();
mat.Translate(p.X, p.Y);
mat.Scale(scaleFactor, scaleFactor);
mat.Translate(-p.X, -p.Y);
_matrix.Multiply(mat);
Refresh();
}
}
#region
private bool OutputOfPixmap(PointF p)
{
var w = this.pixmap.Width;
var h = this.pixmap.Height;
return !(p.X >= 0 && p.X <= (w - 1) && p.Y >= 0 && p.Y <= (h - 1));
}
public FlyShape SetLastLabel(string text, object flags)
{
int index = Shapes.Count - 1;
Shapes[index].label = text;
Shapes[index].flags = flags;
shapesBackups.RemoveAt(shapesBackups.Count - 1);
StoreShapes();
return Shapes[index];
}
public void UndoLastLine()
{
current = Shapes[^1];
Shapes.RemoveAt(Shapes.Count - 1);
current.SetOpen();
// TODO:
switch (CreateMode)
{
case ShapeTypeEnum.Polygon:
case ShapeTypeEnum.LineStrip:
line.Points = new List<PointF> { current[^1], current[0] };
break;
case ShapeTypeEnum.Rectangle:
case ShapeTypeEnum.Line:
case ShapeTypeEnum.Circle:
current.Points = new List<PointF> { current.Points[0] };
break;
case ShapeTypeEnum.Point:
current = null;
break;
default:
break;
}
DrawingPolygon?.Invoke(true);
}
public void UndoLastPoint()
{
if (current == null || current.IsClosed())
{
return;
}
current.PopPoint();
if (current.Length > 0)
{
line[0] = current[-1];
}
else
{
current = null;
DrawingPolygon?.Invoke(false);
}
Invalidate();
}
public void LoadPixmap(Bitmap pixmap, bool clear_shapes = true)
{
if (this.pixmap != null)
{
this.pixmap.Dispose();
}
Init();
this.pixmap = pixmap;
_rcImg.Width = this.pixmap.Width;
_rcImg.Height = this.pixmap.Width;
if (clear_shapes)
{
Shapes = new List<Shape.FlyShape>();
}
FitImage();
this.BackColor = Color.Gray;
Refresh();
this.Focus();
}
/// <summary>
/// 自适应图片,缩放到符合控件尺寸
/// </summary>
public void FitImage()
{
if (null == this.pixmap)
{
return;
}
_matrix = new Matrix();
try
{
// 先将图片缩放到适配控件尺寸
// 宽高比例中的较大值
float wRatio = 1f * pixmap.Width / Width;
float hRatio = 1f * pixmap.Height / Height;
float ratio = 1 / Math.Max(wRatio, hRatio);
_matrix.Scale(ratio, ratio);
_width = (int)(pixmap.Width * ratio);
_height = (int)(pixmap.Height * ratio);
// 再将图片平移到控件中心
// 将plMain的中心转换为图片坐标
PointF pControlCenter = new(Width / 2.0f, Height / 2.0f);
PointF pImgCoordinate = pControlCenter.ToImageCoordinate(_matrix);
//目标坐标减去当前坐标
_matrix.Translate(pImgCoordinate.X - pixmap.Width / 2.0f,
pImgCoordinate.Y - pixmap.Height / 2.0f);
}
catch (Exception)
{
throw;
}
//强制控件使其工作区无效并立即重绘自己和任何子控件
Invalidate();
}
public void LoadShapes(List<Shape.FlyShape> shapes, bool replace = true)
{
if (replace)
{
this.Shapes = new List<Shape.FlyShape>(shapes);
}
else
{
shapes.AddRange(shapes);
}
StoreShapes();
current = null;
hShape = null;
hVertex = -1;
hEdge = -1;
Invalidate();
}
public void SetShapeVisible(Shape.FlyShape shape, bool value)
{
visible[shape] = value;
Invalidate();
}
#endregion
private void Canvas_SizeChanged(object sender, EventArgs e)
{
Invalidate();
}
private void OverrideCursor(Cursor cursor)
{
RestoreCursor();
_preCursor = _cursor;
_cursor = cursor;
this.Cursor = cursor;
}
private void RestoreCursor()
{
this._cursor = _preCursor;
this.Cursor = this._cursor;
}
public void ResetState()
{
this.RestoreCursor();
this.pixmap = null;
this.shapesBackups = new();
this.Invalidate();
}
/// <summary>
/// 鼠标按下事件
/// </summary>
private void FlyCanvas_MouseDown(object? sender, MouseEventArgs e)
{
PointF pos = e.Location.ToImageCoordinate(_matrix);
bool is_shift_pressed = (ModifierKeys & Keys.Shift) == Keys.Shift;
if (MouseButtons.Left == e.Button)
{
if (Drawing())
{
if (current != null) // 画后续的点
{
switch (CreateMode)
{
case ShapeTypeEnum.Polygon:
{
if (!this.line[1].Equals(current[-1]))
{
this.current.AddPoint(this.line[1]);
this.line[0] = this.current[-1];
if (this.current.IsClosed())
{
this.Finalise();
}
}
}
break;
case ShapeTypeEnum.Rectangle: // 矩形
{
this.current.Points = this.line.Points;
OnShapeUpdateEvent?.Invoke(this.current);
this.Finalise();
break;
}
case ShapeTypeEnum.Circle:
case ShapeTypeEnum.Line:
{
this.current.Points = this.line.Points;
OnShapeUpdateEvent?.Invoke(this.current);
this.Finalise();
}
break;
case ShapeTypeEnum.LineStrip:
{
this.current.AddPoint(this.line[1]);
this.line[0] = this.current[-1];
if ((ModifierKeys & Keys.Control) == Keys.Control)
{
this.Finalise();
}
}
break;
default:
break;
}
}
else if (!OutputOfPixmap(pos)) // 画第一个点
{
this.current = new FlyShape()
{
ShapeType = this.CreateMode,
};
this.current.AddPoint(pos, is_shift_pressed ? 0 : 1);
if (CreateMode == ShapeTypeEnum.Point) // 画点
{
this.Finalise();
}
else // 画其他图形
{
if (this.CreateMode == ShapeTypeEnum.Circle)
{
this.current.ShapeType = ShapeTypeEnum.Circle;
}
this.line.Points = new List<PointF>() { pos, pos };
this.line.point_labels = new List<int> { 1, 1 };
this.SetHiding();
this.DrawingPolygon?.Invoke(true);
}
}
}
else if (Editing())
{
if (this.SelectedEdge() && ((ModifierKeys & Keys.Alt) == Keys.Alt))
{
this.AddPointToEdge();
}
else if (this.SelectedVertex()
&& ((ModifierKeys & Keys.Alt) == Keys.Alt)
&& ((ModifierKeys & Keys.Shift) == Keys.Shift))
{
this.RemoveSelectedPoint();
}
bool group_mode = false;
if (AllowMultiSelect)
{
group_mode = (ModifierKeys & Keys.Control) == Keys.Control;
}
this.SelectShapePoint(pos, group_mode);
this.prevPoint = pos;
Invalidate();
} // else if (Editing())
} // if (MouseButtons.Left == e.Button)
else if (MouseButtons.Right == e.Button && this.Editing())
{
bool group_mode = false;
if (AllowMultiSelect)
{
group_mode = (ModifierKeys & Keys.Control) == Keys.Control;
}
if (this.selectedShapes == null
|| (this.hShape != null && !this.selectedShapes.Contains(this.hShape)))
{
this.SelectShapePoint(pos, group_mode);
Invalidate();
}
this.prevPoint = pos;
} // else if (MouseButtons.Right == e.Button && this.Editing())
else if (MouseButtons.Middle == e.Button)
{
if (_rcImg.Contains(pos.ToPoint()))
{
_clickArea = ClickArea.AREA_IMG;
_panBasePoint = pos;
}
} // else if (MouseButtons.Middle == e.Button)
Refresh();
}
private void FlyCanvas_MouseUp(object? sender, MouseEventArgs e)
{
if (MouseButtons.Left == e.Button || MouseButtons.Right == e.Button)
{
_panBasePoint = Point.Empty;
_clickArea = ClickArea.AREA_UNKNOW;
}
if (e.Button == MouseButtons.Right)
{
// menuWithSelection.Show(this, e.Location);
}
else if (e.Button == MouseButtons.Left)
{
if (this.Editing())
{
if (this.hShape != null && this.hShapeIsSelected && !this.movingShape)
{
var shps = this.selectedShapes.Where(shp => !shp.Equals(this.hShape)).ToList();
this.selectedShapes.ForEach(shp => shp.Selected = false);
this.selectedShapes = shps;
this.selectedShapes.ForEach(shp => shp.Selected = true);
this.selectionChanged?.Invoke(this.selectedShapes);
}
}
AfterMouseRelease();
}
}
/// <summary>
/// 鼠标移动事件
/// </summary>
private void FlyCanvas_OnMouseMove(object? sender, MouseEventArgs e)
{
PointF pos = e.Location.ToImageCoordinate(_matrix);
mouseMoved?.Invoke(pos);
this.prevMovePoint = pos;
RestoreCursor();
bool is_shift_pressed = (ModifierKeys & Keys.Shift) == Keys.Shift;
if (Drawing()) // 绘图状态
{
this.line.ShapeType = this.CreateMode;
this.OverrideCursor(CustomCursors.CURSOR_DRAW);
if (this.current == null)
{
return;
}
if (this.OutputOfPixmap(pos))
{
// TODO: 处理超出边界的情况
}
else if (this.snapping
&& this.current.Length > 1
&& this.CreateMode == ShapeTypeEnum.Polygon
&& this.CloseEnough(pos, this.current[0]))
{
pos = this.current[0];
this.OverrideCursor(CustomCursors.CURSOR_POINT);
this.current.HighlightVertex(0, HighlightModeEnum.NEAR_VERTEX);
}
switch (CreateMode)
{
case ShapeTypeEnum.Polygon:
case ShapeTypeEnum.LineStrip:
{
this.line.Points = new() { this.current[-1], pos };
this.line.point_labels = new() { 1, 1 };
}
break;
// case ShapeTypeEnum.Rectangle: // 矩形
// {
//#if false // 改动5
// float minX = Math.Min(this.current[0].X, pos.X);
// float maxX = Math.Max(this.current[0].X, pos.X);
// float minY = Math.Min(this.current[0].Y, pos.Y);
// float maxY = Math.Max(this.current[0].Y, pos.Y);
// List<PointF> tmpPoints = new List<PointF>() {
// new PointF(minX, minY),
// new PointF(maxX, minY),
// new PointF(maxX, maxY),
// new PointF(minX, maxY),
// };
// this.line.Points = tmpPoints;
//#else
// this.line.Points = new() { this.current[0], pos };
//#endif
// this.line.point_labels = new() { 1, 1 };
// this.line.Close();
// }
// break;
case ShapeTypeEnum.Rectangle: // 矩形
{
#if false
float minX = Math.Min(this.current[0].X, pos.X);
float maxX = Math.Max(this.current[0].X, pos.X);
float minY = Math.Min(this.current[0].Y, pos.Y);
float maxY = Math.Max(this.current[0].Y, pos.Y);
List<PointF> tmpPoints = new List<PointF>() {
new PointF(minX, minY),
new PointF(maxX, minY),
new PointF(maxX, maxY),
new PointF(minX, maxY),
};
this.line.Points = tmpPoints;
#else
this.line.Points = new() { this.current[0], pos };
#endif
this.line.point_labels = new() { 1, 1 };
this.line.Close();
}
break;
case ShapeTypeEnum.Circle:
{
this.line.Points = new() { this.current[0], pos };
this.line.point_labels = new() { 1, 1 };
this.line.ShapeType = ShapeTypeEnum.Circle;
OnShapeUpdateEvent?.Invoke(this.line);
}
break;
case ShapeTypeEnum.Line:
{
this.line.Points = new() { this.current[0], pos };
this.line.point_labels = new() { 1, 1 };
this.line.Close();
OnShapeUpdateEvent?.Invoke(this.line);
}
break;
case ShapeTypeEnum.Point:
{
this.line.Points = new() { this.current[0] };
this.line.point_labels = new() { 1 };
this.line.Close();
}
break;
default:
break;
}
Refresh();
this.current.HighlightClear();
return;
}
// 多边形复制移动
if (e.Button == MouseButtons.Right)
{
if (this.selectedShapesCopy != null
&& this.selectedShapesCopy.Count > 0
&& this.prevPoint != null)
{
this.OverrideCursor(CustomCursors.CURSOR_MOVE);
this.BoundedMoveShapes(this.selectedShapesCopy, pos);
}
else if (this.selectedShapes != null && this.selectedShapes.Count > 0)
{
this.selectedShapesCopy = this.selectedShapes.Select(shp => shp.Copy()).ToList();
}
Invalidate();
return;
}
// 多边形/节点 移动
if (e.Button == MouseButtons.Left)
{
if (this.SelectedVertex())
{
this.BoundedMoveVertex(pos);
Invalidate();
this.movingShape = true;
}
else if (this.selectedShapes != null
&& this.selectedShapes.Count > 0
&& this.prevPoint != null)
{
this.OverrideCursor(CustomCursors.CURSOR_MOVE);
this.BoundedMoveShapes(this.selectedShapes, pos);
OnShapeMoving?.Invoke(this.selectedShapes);
Invalidate();
this.movingShape = true;
}
return;
}
//# Just hovering over the canvas, 2 possibilities:
//# - Highlight shapes
//# - Highlight vertex
//# Update shape/vertex fill and tooltip value accordingly.
var tmpShapes = this.Shapes.Where(shp => this.IsVisible(shp)).Reverse();
if (tmpShapes.Any())
{
bool found = false;
foreach (var shape in tmpShapes)
{
// 寻找距离鼠标最近的节点
int index = shape.NearestVertex(pos, this.epsilon);
int index_edge = shape.NearestEdge(pos, this.epsilon);
if (index >= 0)
{
if (this.SelectedVertex())
{
this.hShape?.HighlightClear();
}
this.prevhVertex = this.hVertex = index;
this.prevhShape = this.hShape = shape;
this.prevhEdge = this.hEdge;
this.hEdge = -1;
shape.HighlightVertex(index, HighlightModeEnum.MOVE_VERTEX);
this.OverrideCursor(CustomCursors.CURSOR_POINT);
// TODO: Tooltip
Invalidate();
found = true;
break;
}
else if (index_edge >= 0 && shape.CanAddPoint())
{
if (this.SelectedVertex())
{
this.hShape?.HighlightClear();
}
this.prevhVertex = this.hVertex;
this.hVertex = -1;
this.prevhShape = this.hShape = shape;
this.prevhEdge = this.hEdge = index_edge;
this.OverrideCursor(CustomCursors.CURSOR_POINT);
// TODO: Tooltip
Invalidate();
found = true;
break;
}
else if (shape.ContainsPoint(pos))
{
if (this.SelectedVertex())
{
this.hShape?.HighlightClear();
}
this.prevhVertex = this.hVertex;
this.hVertex = -1;
this.prevhShape = this.hShape = shape;
this.prevhEdge = this.hEdge;
this.hEdge = -1;
this.OverrideCursor(CustomCursors.CURSOR_GRAB);
Invalidate();
found = true;
break;
}
}
if (!found)
{
UnHighlight();
}
this.vertexSelected?.Invoke(this.hVertex >= 0);
}
// 鼠标中键移动触发移动事件
if (MouseButtons.Middle == e.Button)
{
if (PointF.Empty == _panBasePoint)
{
return;
}
switch (_clickArea)
{
case ClickArea.AREA_IMG: // 点击了图像区域
{
PointF p = e.Location.ToImageCoordinate(_matrix);
float x = p.X - _panBasePoint.X;
float y = p.Y - _panBasePoint.Y;
_matrix.Translate(x, y);
break;
}
}
}
Refresh();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
try
{
//获取表示控件的工作区的矩形
Rectangle rect = ClientRectangle;
Graphics oriGraphics = e.Graphics;
//设置平滑程度为高质量,减少抗锯齿的出现
oriGraphics.SmoothingMode = SmoothingMode.HighQuality;
// 双缓冲绘图
//获取当前应用程序域的 BufferedGraphicsContext此实例管理该应用程序的所有默认双缓冲
BufferedGraphicsContext currentContext = BufferedGraphicsManager.Current;
//BufferedGraphics 对象管理与呈现图面(例如窗体或控件)关联的内存缓冲
BufferedGraphics myBuffer = currentContext.Allocate(oriGraphics, rect);
//实例化一个直接表示内存缓冲的 Graphics 对象,可将图形呈现到内存缓冲,绘制到此对象中
Graphics bufferGraphics = myBuffer.Graphics;
bufferGraphics.SmoothingMode = SmoothingMode.HighQuality;
//高质量低速度呈现
bufferGraphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
//使用的插值算法
bufferGraphics.InterpolationMode = InterpolationMode.NearestNeighbor;
//清除整个绘图面并以指定背景色填充,Console.BackColor指获取当前控件的背景色
bufferGraphics.Clear(Color.FromArgb(0xf0, 0xf0, 0xf0));
bufferGraphics.MultiplyTransform(_matrix);
#region
if (pixmap != null)
{
try
{
bufferGraphics.DrawImage(pixmap, 0, 0, pixmap.Width, pixmap.Height);
//if (_path != null)
//{
// bufferGraphics.DrawPath(new Pen(Brushes.Yellow, 1f), _path);
//}
}
catch { }
}
else
{
pixmap = null;
return;
}
#endregion
#region
foreach (var shape in Shapes)
{
if ((shape.Selected || !this._hideBackround)
&& this.IsVisible(shape))
{
shape.fill = shape.Selected || (shape == this.hShape);
shape.Paint(bufferGraphics);
}
}
if (this.current != null)
{
this.current.Paint(bufferGraphics);
this.line.Paint(bufferGraphics);
}
if (this.selectedShapesCopy != null)
{
foreach (var s in this.selectedShapesCopy)
{
s.Paint(bufferGraphics);
}
}
// TODO:
this.FillDrawing = true;
if (this.FillDrawing
&& this.CreateMode == ShapeTypeEnum.Polygon
&& this.current != null
&& this.current.Length >= 2)
{
var drawing_shape = this.current.Copy();
if (drawing_shape.fill_color.A == 0)
{
drawing_shape.fill_color = Color.FromArgb(64, drawing_shape.fill_color);
}
drawing_shape.AddPoint(this.line[1]);
drawing_shape.fill = true;
drawing_shape.Paint(bufferGraphics);
}
#endregion
#region
// this.OutsideShapes.ForEach(shp => shp.Paint(bufferGraphics));
for (int i = 0; i < OutsideShapes.Count; i++)
{
OutsideShapes[i].Paint(bufferGraphics);
}
#endregion
myBuffer.Render(oriGraphics);
//释放资源
bufferGraphics.Dispose();
myBuffer.Dispose();
currentContext.Dispose();
}
catch (Exception ex)
{
throw;
}
}
private void SelectShapePoint(PointF point, bool multiple_selection_mode)
{
if (this.SelectedVertex())
{
this.hShape?.HighlightVertex(this.hVertex, HighlightModeEnum.MOVE_VERTEX);
}
else
{
List<Shape.FlyShape> reversedShapes = Shapes.ToList();
reversedShapes.Reverse();
foreach (var shape in reversedShapes)
{
if (this.IsVisible(shape) && shape.ContainsPoint(point))
{
this.SetHiding();
if (this.selectedShapes.Contains(shape))
{
this.hShapeIsSelected = true;
}
else
{
if (multiple_selection_mode)
{
shape.Selected = true;
this.selectedShapes.Add(shape);
this.selectionChanged?.Invoke(this.selectedShapes);
}
else
{
shape.Selected = true;
this.selectedShapes = new List<FlyShape>() { shape };
this.selectionChanged?.Invoke(this.selectedShapes);
}
this.hShapeIsSelected = false;
}
return;
}
}
}
this.DeSelectShape();
}
private void RemoveSelectedPoint()
{
throw new NotImplementedException();
}
private void AddPointToEdge()
{
throw new NotImplementedException();
}
private bool BoundedMoveShapes(List<FlyShape> shapes, PointF pos)
{
if (this.OutputOfPixmap(pos))
{
return false;
}
PointF o1 = new PointF(
pos.X + this.offsets[0].X,
pos.Y + this.offsets[0].Y);
if (this.OutputOfPixmap(o1))
{
pos = new PointF(
pos.X - Math.Min(0, o1.X),
pos.Y - Math.Min(0, o1.Y));
}
PointF o2 = new PointF(
pos.X + this.offsets[1].X,
pos.Y + this.offsets[1].Y);
if (this.OutputOfPixmap(o1))
{
pos = new PointF(
pos.X + Math.Min(0, this.pixmap.Width - o2.X),
pos.Y + Math.Min(0, this.pixmap.Height - o2.Y));
}
try
{
PointF dp = new PointF(pos.X - this.prevPoint.X, pos.Y - this.prevPoint.Y);
foreach (FlyShape shape in shapes)
{
shape.MoveBy(dp);
}
this.prevPoint = pos;
return true;
}
catch
{
}
return false;
}
private void BoundedMoveVertex(PointF pos)
{
PointF point = this.hShape[this.hVertex];
if (this.OutputOfPixmap(pos))
{
pos = this.IntersectionPoint(point, pos);
}
this.hShape.IsVertexMoving = true;
this.hShape.MoveVertexBy(this.hVertex, new PointF(pos.X - point.X, pos.Y - point.Y));
OnShapeUpdateEvent?.Invoke(hShape);
}
private PointF IntersectionPoint(PointF p1, PointF p2)
{
Size size = this.pixmap.Size;
List<PointF> points = new List<PointF>()
{
new PointF(0, 0),
new PointF(size.Width - 1, 0),
new PointF(size.Width - 1, size.Height - 1),
new PointF(0, size.Height - 1),
};
float x1 = Math.Min(Math.Max(p1.X, 0), size.Width - 1);
float y1 = Math.Min(Math.Max(p1.Y, 0), size.Height - 1);
float x2 = p2.X;
float y2 = p2.Y;
// Get the intersection details
var intersections = IntersectingEdges(
new PointF(x1, y1),
new PointF(x2, y2),
points);
var closestIntersection = intersections.OrderBy(result => result.distance).FirstOrDefault();
if (closestIntersection.Equals(default((double distance, int index, PointF intersectionPoint))))
{
return PointF.Empty; // No intersection found
}
var (d, i, intersection) = closestIntersection;
// Define the edge points
float x3 = points[i].X;
float y3 = points[i].Y;
float x4 = points[(i + 1) % 4].X;
float y4 = points[(i + 1) % 4].Y;
if (intersection == new PointF(x1, y1))
{
// Handle cases where the previous point is on one of the edges.
if (x3 == x4)
{
return new PointF(x3, Math.Min(Math.Max(0, y2), Math.Max(y3, y4)));
}
else // y3 == y4
{
return new PointF(Math.Min(Math.Max(0, x2), Math.Max(x3, x4)), y3);
}
}
return intersection;
}
private IEnumerable<(double distance, int index, PointF intersectionPoint)> IntersectingEdges(PointF point1, PointF point2, List<PointF> points)
{
float x1 = point1.X;
float y1 = point1.Y;
float x2 = point2.X;
float y2 = point2.Y;
for (int i = 0; i < 4; i++)
{
float x3 = points[i].X;
float y3 = points[i].Y;
float x4 = points[(i + 1) % 4].X;
float y4 = points[(i + 1) % 4].Y;
float denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
float nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
float nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3);
if (denom == 0)
{
continue;
}
float ua = nua / denom;
float ub = nub / denom;
if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1)
{
float x = x1 + ua * (x2 - x1);
float y = y1 + ua * (y2 - y1);
PointF m = new PointF((x3 + x4) / 2, (y3 + y4) / 2);
float d = PointHelper.Distance(m, new PointF(x2, y2));
yield return (d, i, new PointF(x, y));
}
}
}
private bool CloseEnough(PointF p1, PointF p2)
{
var d1 = PointHelper.Distance(p1, p2);
//var d2 = (this.epsilon / this.Scale);
//return d1 < d2;
// TODO:
//return false;
return d1 < 16;
}
private void InitTask()
{
TaskFactory taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.LongRunning);
taskFactory.StartNew(MenuWithSelectionEvent);
}
private void AfterMouseRelease()
{
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker(AfterMouseRelease));
return;
}
if (this.movingShape && this.hShape != null)
{
this.hShape.IsVertexMoving = false;
int index = this.Shapes.IndexOf(this.hShape);
if (shapesBackups.Count > 0)
{
if (this.shapesBackups[this.shapesBackups.Count - 1][index].Points
!= this.Shapes[index].Points)
{
this.StoreShapes();
this.ShapeMoved?.Invoke();
}
}
this.movingShape = false;
}
}
private bool CanCloseShape()
{
var b1 = this.Drawing();
var b2 = this.current != null && this.current.Length > 2;
return b1 && b2;
}
private void FlyCanvas_MouseDoubleClick(object? sender, MouseEventArgs e)
{
if (this.double_click != DoubleClickActionEnum.Close)
{
return;
}
if (this.CreateMode == ShapeTypeEnum.Polygon && this.CanCloseShape())
{
this.Finalise();
}
}
//private List<Shape.FlyShape> TestReflect()
//{
// List<Shape.FlyShape> shapes = new List<Shape.FlyShape>();
// List<string> names = new List<string>();
// // 获取对象的类型
// Type type = this.GetType();
// // 获取所有公共字段
// FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
// foreach (var field in fields)
// {
// if (field.FieldType.IsSubclassOf(typeof(Shape.FlyShape)) || field.FieldType == typeof(Shape.FlyShape))
// {
// shapes.Add((Shape.FlyShape)field.GetValue(this));
// names.Add(field.Name);
// }
// }
// // 获取所有公共属性
// PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
// foreach (var property in properties)
// {
// if (property.PropertyType.IsSubclassOf(typeof(Shape.FlyShape)) || property.PropertyType == typeof(Shape.FlyShape))
// {
// shapes.Add((Shape.FlyShape)property.GetValue(this));
// names.Add(property.Name);
// }
// }
// return shapes;
//}
/// <summary>
/// 重写 ProcessDialogKey 使控件可以监听方向键与删除键
/// </summary>
protected override bool ProcessDialogKey(Keys keyData)
{
if (keyData == Keys.Up || keyData == Keys.Down ||
keyData == Keys.Left || keyData == Keys.Right || keyData == Keys.Delete)
{
return false;
}
else
{
return base.ProcessDialogKey(keyData);
}
}
//protected override void OnKeyDown(KeyEventArgs e)
private void FlyCanvas_KeyPress(object? sender, KeyPressEventArgs e)
{
//var modifiers = e.Modifiers;
//var key = e.KeyCode;
Debug.WriteLine("");
//if (this.Drawing())
//{
// if (key == Keys.Escape && this.current != null)
// {
// this.current = null;
// this.DrawingPolygon?.Invoke(false);
// this.Invalidate();
// }
// else if (key == Keys.Return && this.CanCloseShape())
// {
// this.Finalise();
// }
// else if ((ModifierKeys & Keys.Alt) == Keys.Alt)
// {
// this.snapping = false;
// }
//}
//else if (Editing())
//{
// switch (key)
// {
// case Keys.Up:
// this.MoveByKeyboard(new PointF(0f, -MOVE_SPEED));
// break;
// case Keys.Down:
// this.MoveByKeyboard(new PointF(0f, MOVE_SPEED));
// break;
// case Keys.Left:
// this.MoveByKeyboard(new PointF(-MOVE_SPEED, 0f));
// break;
// case Keys.Right:
// this.MoveByKeyboard(new PointF(MOVE_SPEED, 0f));
// break;
// case Keys.Delete:
// DeleteSelected();
// Invalidate();
// break;
// default:
// break;
// }
//}
}
private void FlyCanvas_KeyDown(object? sender, KeyEventArgs e)
{
var modifiers = e.Modifiers;
var key = e.KeyCode;
if (this.Drawing())
{
if (key == Keys.Escape && this.current != null)
{
this.current = null;
this.DrawingPolygon?.Invoke(false);
this.Invalidate();
}
else if (key == Keys.Return && this.CanCloseShape())
{
this.Finalise();
}
else if ((ModifierKeys & Keys.Alt) == Keys.Alt)
{
this.snapping = false;
}
}
else if (Editing())
{
switch (key)
{
case Keys.Up:
this.MoveByKeyboard(new PointF(0f, -MOVE_SPEED));
break;
case Keys.Down:
this.MoveByKeyboard(new PointF(0f, MOVE_SPEED));
break;
case Keys.Left:
this.MoveByKeyboard(new PointF(-MOVE_SPEED, 0f));
break;
case Keys.Right:
this.MoveByKeyboard(new PointF(MOVE_SPEED, 0f));
break;
case Keys.Delete:
DeleteSelected();
Invalidate();
break;
default:
break;
}
}
}
private void MoveByKeyboard(PointF offset)
{
if (this.selectedShapes == null)
{
return;
}
this.BoundedMoveShapes(this.selectedShapes,
new PointF(this.prevPoint.X + offset.X, this.prevPoint.Y + offset.Y));
this.Invalidate();
this.movingShape = true;
}
/// <summary>
/// 清理所有正在使用的资源。
/// </summary>
/// <param name="disposing">如果应释放托管资源,为 true否则为 false。</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
isControlAlive = false; // 设置标志,停止循环
menuCloseEvent.Set(); // 唤醒等待的线程,确保退出循环
components.Dispose();
}
base.Dispose(disposing);
}
#region
private bool isControlAlive = true;
private bool menuWithSelectionItemClicked = false;
private AutoResetEvent menuCloseEvent = new(false);
private void MenuWithSelectionEvent()
{
while (isControlAlive)
{
try
{
bool ret = menuCloseEvent.WaitOne();
if (!ret)
{
continue;
}
if (menuWithSelectionItemClicked) // 点击了菜单项
{
menuWithSelectionItemClicked = false;
}
else if (this.selectedShapesCopy != null && this.selectedShapesCopy.Count > 0) // 未点击菜单项
{
this.selectedShapesCopy = new List<Shape.FlyShape>();
Invalidate();
AfterMouseRelease();
}
}
catch
{
}
}
}
#region Shape时的右键菜单点击事件
private void menuItemCopyToHere_Click(object sender, EventArgs e)
{
OnMenuItemCopyToHere?.Invoke();
AfterMouseRelease();
}
private void menuItemMoveToHere_Click(object sender, EventArgs e)
{
OnMenuItemMoveToHere?.Invoke();
AfterMouseRelease();
}
private void menuWithSelection_Closed(object sender, ToolStripDropDownClosedEventArgs e)
{
menuCloseEvent.Set();
Debug.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\t menuWithSelection_Closed");
}
private void menuWithSelection_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
menuWithSelectionItemClicked = true;
Debug.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\t menuWithSelection_ItemClicked");
}
#endregion
#endregion
public bool EndMove(bool copy)
{
if (!(this.selectedShapes != null && this.selectedShapes.Count > 0
&& this.selectedShapesCopy != null && this.selectedShapesCopy.Count > 0
&& this.selectedShapesCopy.Count == this.selectedShapes.Count))
{
return false;
}
if (copy)
{
for (int i = 0; i < this.selectedShapesCopy.Count; i++)
{
Shape.FlyShape shape = this.selectedShapesCopy[i];
this.Shapes.Add(shape);
this.selectedShapes[i].Selected = false;
this.selectedShapes[i] = shape;
}
}
else
{
for (int i = 0; i < this.selectedShapesCopy.Count; i++)
{
var shape = this.selectedShapesCopy[i];
this.selectedShapes[i].Points = shape.Points;
}
}
this.selectedShapesCopy = new List<Shape.FlyShape>();
this.Invalidate();
this.StoreShapes();
return true;
}
public void SelectedShapes(List<Shape.FlyShape> shapes)
{
this.SetHiding();
this.selectionChanged?.Invoke(shapes);
this.Invalidate();
}
public void DeSelectecShape()
{
if (this.selectedShapes != null && this.selectedShapes.Count > 0)
{
this.SetHiding(false);
this.selectionChanged?.Invoke(new List<Shape.FlyShape>());
this.hShapeIsSelected = false;
this.Invalidate();
}
}
///// <summary>
///// 设置编辑状态
///// </summary>
///// <param name="value">false创建图形true编辑图形</param>
//public void SetEditing(bool value = true)
//{
// this._isEditMode = !value;
// if (!this._isEditMode)
// {
// // CREATE -> EDIT
// //Repaint();
// }
// else
// {
// // EDIT -> CREATE
// UnHighlight();
// DeSelectShape();
// }
//}
/// <summary>
/// 设置编辑状态
/// </summary>
/// <param name="value">false创建图形true编辑图形</param>
public void StopDraw()
{
this._isEditMode = true;
// EDIT -> CREATE
UnHighlight();
DeSelectShape();
}
/// <summary>
/// 设置为绘图状态
/// </summary>
public void StartDraw(ShapeTypeEnum shapeType)
{
this._isEditMode = false;
this._createMode = shapeType;
// CREATE -> EDIT
//Repaint();
}
private void Canvas_shapeSelectionChanged(List<FlyShape> selected_shapes)
{
//this._noSelectionsSlot = true;
//this.canvas.selectedShapes.ForEach(shp => shp.Selected = false);
//this.dgvLabelList.ClearSelection();
//this.selectedShapes = selected_shapes;
//this.selectedShapes.ForEach(shape =>
//{
// shape.Selected = true;
// foreach (DataGridViewRow row in this.dgvLabelList.Rows)
// {
// if (row.IsNewRow)
// {
// continue;
// }
// if (row.Tag is not ShapeListItemTag listItemTag)
// {
// continue;
// }
// if (listItemTag.Shape == shape)
// {
// row.Selected = true;
// }
// }
//});
//this._noSelectionsSlot = false;
//bool n_selected = selected_shapes.Count > 0;
//this.btnDeleteSelectedShape.Enabled = n_selected;
//this.btnCopySelectedShape.Enabled = n_selected;
//this.btnEditMode.Enabled = n_selected;
}
public void ClearDraw()
{
this.OutsideShapes.Clear();
Invalidate();
}
public void DrawCircle(PointF center, float r, float lineWidth = 2)
{
FlyShape flyShape = new FlyShape();
flyShape.Points.Add(center);
flyShape.Points.Add(new PointF(center.X + r, center.Y));
flyShape.ShapeType = ShapeTypeEnum.Circle;
flyShape.line_color = Color.Red;
flyShape.LineWidth = lineWidth;
OutsideShapes.Add(flyShape);
Invalidate();
}
/// <summary>
///
/// </summary>
/// <param name="p1"></param>
/// <param name="p2"></param>
/// <param name="rectWidth"></param>
public void DrawLine(PointF p1, PointF p2, float rectWidth=0)
{
FlyShape shp = new FlyShape();
shp.Points.Add(p1);
shp.Points.Add(p2);
shp.ShapeType = ShapeTypeEnum.Line;
shp.line_color = Color.Red;
shp.LineWidth = 2;
if (rectWidth > 0)
{
shp.IsDrawLineVirtualRect = true;
shp.LineVirtualRectWidth = rectWidth;
}
OutsideShapes.Add(shp);
Invalidate();
}
public void DrawRectangle(PointF p1, PointF p2, float rotate)
{
FlyShape flyShape = new FlyShape();
flyShape.Points.Add(p1);
//flyShape.Points.Add(new PointF(p2.X, p1.Y)); // 改动6
flyShape.Points.Add(p2);
//flyShape.Points.Add(new PointF(p1.X, p2.Y)); // 改动6
flyShape.ShapeType = ShapeTypeEnum.Rectangle;
flyShape.line_color = Color.Red;
flyShape.LineWidth = 2;
flyShape._currentRotateAngle = rotate;
OutsideShapes.Add(flyShape);
Invalidate();
}
}
}