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 zoomRequest; public event Action ZoomRequestF; public event Action scrollRequest; public event Action newShape; public event Action> selectionChanged; public event Action ShapeMoved; public event Action> OnShapeMoving; public event Action DrawingPolygon; public event Action vertexSelected; public event Action mouseMoved; public event Action 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 Shapes { get; set; } = new List(); /// /// 外部绘制的 /// public List OutsideShapes { get; set; } = new List(); public List> shapesBackups = new(); private FlyShape? current = null; public List selectedShapes = new List(); private List selectedShapesCopy = new List(); 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; /// /// 是否允许多选 /// public bool AllowMultiSelect { get; set; } = false; public int _width { get; private set; } public int _height { get; private set; } //public List MenuStripsWithoutSelection = new List(); //public List MenuStripsWithSelection = new List(); /// /// 缩放比例,该参数必须大于0 /// 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 visible = new Dictionary(); 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; /// /// 点击的区域 /// internal enum ClickArea { /// /// 未知区域 /// AREA_UNKNOW, /// /// 图片区域 /// AREA_IMG, /// /// 缺陷元素区域 /// AREA_DEFECT, } private ClickArea _clickArea = ClickArea.AREA_UNKNOW; /// /// 矩形框 /// private Rectangle _rcImg = new Rectangle(0, 0, 0, 0); /// /// 变换矩阵 /// private Matrix _matrix = new Matrix(); public Matrix Matrix => _matrix.Clone(); //private Bitmap image; //private List rectangles = new List(); //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; } } /// /// 图形类型 /// public ShapeTypeEnum CreateMode { get { return _createMode; } set { _createMode = value; } } public void StoreShapes() { var shapesBackup = new List(); 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 shapesBackup = shapesBackups[^1]; // 获取最后的备份而不移除它 shapesBackups.RemoveAt(shapesBackups.Count - 1); // 移除最新的备份 // 恢复形状 this.Shapes = shapesBackup; this.selectedShapes = new List(); // 清空选中的形状 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()); this.hShapeIsSelected = false; Invalidate(); } } public List DeleteSelected() { List deleted_shapes = new List(); if (this.selectedShapes != null) { foreach (var shape in this.selectedShapes) { this.Shapes.Remove(shape); deleted_shapes.Add(shape); } this.StoreShapes(); this.selectedShapes = new List(); 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 { current[^1], current[0] }; break; case ShapeTypeEnum.Rectangle: case ShapeTypeEnum.Line: case ShapeTypeEnum.Circle: current.Points = new List { 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(); } FitImage(); this.BackColor = Color.Gray; Refresh(); this.Focus(); } /// /// 自适应图片,缩放到符合控件尺寸 /// 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 shapes, bool replace = true) { if (replace) { this.Shapes = new List(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(); } /// /// 鼠标按下事件 /// 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() { pos, pos }; this.line.point_labels = new List { 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(); } } /// /// 鼠标移动事件 /// 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 tmpPoints = new List() { // 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 tmpPoints = new List() { 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 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() { 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 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 points = new List() { 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 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 TestReflect() //{ // List shapes = new List(); // List names = new List(); // // 获取对象的类型 // 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; //} /// /// 重写 ProcessDialogKey 使控件可以监听方向键与删除键 /// 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; } /// /// 清理所有正在使用的资源。 /// /// 如果应释放托管资源,为 true;否则为 false。 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(); 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(); this.Invalidate(); this.StoreShapes(); return true; } public void SelectedShapes(List 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()); this.hShapeIsSelected = false; this.Invalidate(); } } ///// ///// 设置编辑状态 ///// ///// false:创建图形,true:编辑图形 //public void SetEditing(bool value = true) //{ // this._isEditMode = !value; // if (!this._isEditMode) // { // // CREATE -> EDIT // //Repaint(); // } // else // { // // EDIT -> CREATE // UnHighlight(); // DeSelectShape(); // } //} /// /// 设置编辑状态 /// /// false:创建图形,true:编辑图形 public void StopDraw() { this._isEditMode = true; // EDIT -> CREATE UnHighlight(); DeSelectShape(); } /// /// 设置为绘图状态 /// public void StartDraw(ShapeTypeEnum shapeType) { this._isEditMode = false; this._createMode = shapeType; // CREATE -> EDIT //Repaint(); } private void Canvas_shapeSelectionChanged(List 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(); } /// /// /// /// /// /// 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(); } } }