using System; using System.Collections.Generic; using System.Drawing.Drawing2D; using System.Linq; using System.Runtime.Serialization.Formatters.Binary; using System.Runtime.Serialization; using System.Text; using System.Threading.Tasks; using CanFly.Canvas.Helper; using Newtonsoft.Json; using System.Diagnostics; using System.Net.NetworkInformation; using System.Drawing; namespace CanFly.Canvas.Shape { [Serializable] public class FlyShape { private const float DFT_VTX_EPSILON = 4f; private float _epsilon = DFT_VTX_EPSILON; public float LineWidth { get; set; } = 2f; #region Shape颜色 #region drawing public Color line_color = Color.FromArgb(128, 0, 255, 0); public Color fill_color = Color.FromArgb(64, 0, 0, 0); public Color vertex_fill_color = Color.FromArgb(255, 0, 255, 0); #endregion #region selecting / hovering public Color select_line_color = Color.FromArgb(255, 255, 255, 255); public Color select_fill_color = Color.FromArgb(64, 0, 255, 0); public Color hvertex_fill_color = Color.FromArgb(255, 255, 255, 255); #endregion #endregion private PointTypeEnum point_type = PointTypeEnum.ROUND; private float point_size = 8.0f; private float _scale = 1.0f; private float scale { get { return _scale; } set { _scale = value; } } private ShapeTypeEnum _shape_type; private Matrix _matrix = new Matrix(); public ShapeTypeEnum ShapeType { get => _shape_type; set { _shape_type = value; } } public string label = ""; public int? group_id = null; private List _points { get; set; } = new List(); public List Points { get { return _points; } set { this._points = value; } } private List _pointsRaw = new List(); /// /// 辅助节点 /// public List GuidePoints = new List(); public float _currentRotateAngle; private bool _isRotating = false; public List point_labels = new List(); private ShapeTypeEnum _shape_type_raw; /// /// 是否填充多边形 使用:select_fill_color 或 fill_color 填充 /// public bool fill = false; public bool Selected { get; set; } = false; public object? flags; public string description = ""; private List other_data = new List(); private int _highlightIndex = -1; private HighlightModeEnum _highlightMode = HighlightModeEnum.NEAR_VERTEX; private Dictionary _highlightSettings = new Dictionary() { { HighlightModeEnum.NEAR_VERTEX,new HighlightSetting(4,PointTypeEnum.ROUND)}, { HighlightModeEnum.MOVE_VERTEX,new HighlightSetting(1.5f,PointTypeEnum.SQUARE)}, }; private bool _closed = false; private Color _vertex_fill_color; /// /// 当图形是Line时,是否绘制辅助矩形框 /// public bool IsDrawLineVirtualRect { get; set; } = false; /// /// 画Line时辅助矩形的宽度 /// public float LineVirtualRectWidth = 40; public PointF[] LineVirtualRectPoints = new PointF[4]; public FlyShape() { } private PointF ScalePoint(PointF point) { return point; //return new PointF( // (float)(point.X * scale), // (float)(point.Y * scale)); } public void Close() { this._closed = true; } public void AddPoint(PointF point, int label = 1) { if (_points != null && _points.Count > 0 && point.Equals(_points.ElementAt(0))) { Close(); } else { if (_points.Count > 0 && this[-1].Equals(point)) { return; } _points.Add(point); point_labels.Add(label); } } public bool CanAddPoint() { return ShapeType == ShapeTypeEnum.Polygon || ShapeType == ShapeTypeEnum.LineStrip; } public PointF? PopPoint() { if (_points != null && _points.Count > 0) { if (point_labels != null && point_labels.Count > 0) { point_labels.RemoveAt(point_labels.Count - 1); } PointF lastPoint = _points[_points.Count - 1]; _points.RemoveAt(_points.Count - 1); return lastPoint; } return null; } public void InsertPoint(int i, PointF point, int label = 1) { _points.Insert(i, point); point_labels.Insert(i, label); } public void RemovePoint(int i) { if (!CanAddPoint()) { return; } if (ShapeType == ShapeTypeEnum.Polygon && _points.Count <= 3) { return; } else if (ShapeType == ShapeTypeEnum.LineStrip && _points.Count <= 2) { return; } _points.RemoveAt(_points.Count - 1); point_labels.RemoveAt(point_labels.Count - 1); } public bool IsClosed() => _closed; public void SetOpen() { _closed = false; } #region 矩形辅助函数 /// /// 矩形模式下,选中的点索引 /// [JsonIgnore] private int _rectSelectedVertex = -1; [JsonIgnore] private PointF _rectSelectedMoveVertex; [JsonIgnore] private PointF _rectCenterPoint; [JsonIgnore] private bool isVertexMoving; /// /// 正在移动节点 /// [JsonIgnore] public bool IsVertexMoving { get { return isVertexMoving; } internal set { //float centerX = (_points[0].X + _points[2].X) / 2f; //float centerY = (_points[0].Y + _points[2].Y) / 2f; //_rectCenterVertex = new PointF(centerX, centerY); isVertexMoving = value; } } //private PointF[] TransformPoints(List points, PointF center, float angle) //{ // PointF[] ptsArray = points.ToArray(); // using (Matrix matrix = new Matrix()) // { // matrix.RotateAt(angle, center); // matrix.TransformPoints(ptsArray); // } // return ptsArray; //} //GraphicsPath vrtx_path = new GraphicsPath(); #endregion public void Paint(Graphics painter) { if (_points == null || _points.Count == 0) { return; } Color color = Selected ? select_line_color : line_color; using Pen pen = new Pen(color, LineWidth); // Create paths GraphicsPath line_path = new GraphicsPath(); GraphicsPath vrtx_path = new GraphicsPath(); GraphicsPath guide_vrtx_path = new GraphicsPath(); switch (ShapeType) { //case ShapeTypeEnum.Rectangle: // { // if (_points.Count == 2) // { // float centerX = (_points[0].X + _points[1].X) / 2f; // float centerY = (_points[0].Y + _points[1].Y) / 2f; // _rectCenterPoint = new PointF(centerX, centerY); // line_path.StartFigure(); // if (_points[1].X < _points[0].X) // { // _points[1] = new PointF(_points[0].X + LineWidth / 2f, _points[1].Y); // } // if (_points[1].Y < _points[0].Y) // { // _points[1] = new PointF(_points[1].X, _points[0].Y + LineWidth / 2f); // } // //float x = Math.Min(_points[0].X, _points[1].X); // //float y = Math.Min(_points[0].Y, _points[1].Y); // float w = Math.Abs(ScalePoint(_points[1]).X - ScalePoint(_points[0]).X); // float h = Math.Abs(ScalePoint(_points[1]).Y - ScalePoint(_points[0]).Y); // RectangleF drawRect = new(new PointF(_points[0].X, _points[0].Y), new SizeF(w, h)); // bool bRotated = false; // NomalizeRotateAngle(); // Matrix oMatrix = null; // if (_currentRotateAngle > 0) // { // // Create rotation matrix // oMatrix = new Matrix(); // oMatrix.RotateAt(_currentRotateAngle, _rectCenterPoint, MatrixOrder.Append); // painter.Transform = oMatrix; // bRotated = true; // } // //Store rectangle region // //Region _drawRectRegion = new Region(drawRect); // if (oMatrix != null) // line_path.Transform(oMatrix); // line_path.AddRectangle(drawRect); // // Reset transform // if (bRotated) // { // bRotated = false; // painter.ResetTransform(); // } // //_matrix.Reset(); // //_matrix.RotateAt(_currentRotateAngle, new PointF( // // (_points[0].X + _points[1].X) / 2, // // (_points[0].Y + _points[1].Y) / 2)); // //line_path.Transform(_matrix); // //line_path.AddPolygon(_pointsRaw.ToArray()); // } // if (_regionVertex.Length != _points.Count) // { // _regionVertex = new Region[_points.Count]; // } // for (int i = 0; i < _points.Count; i++) // { // DrawVertex(vrtx_path, i); // } // vrtx_path.Transform(_matrix); // } // break; case ShapeTypeEnum.Rectangle: { if (_points.Count == 2) { float centerX = (_points[0].X + _points[1].X) / 2f; float centerY = (_points[0].Y + _points[1].Y) / 2f; _rectCenterPoint = new PointF(centerX, centerY); line_path.StartFigure(); if (_points[1].X < _points[0].X) { _points[1] = new PointF(_points[0].X + LineWidth / 2f, _points[1].Y); } if (_points[1].Y < _points[0].Y) { _points[1] = new PointF(_points[1].X, _points[0].Y + LineWidth / 2f); } float w = Math.Abs(ScalePoint(_points[1]).X - ScalePoint(_points[0]).X); float h = Math.Abs(ScalePoint(_points[1]).Y - ScalePoint(_points[0]).Y); RectangleF drawRect = new(new PointF(_points[0].X, _points[0].Y), new SizeF(w, h)); line_path.AddRectangle(drawRect); _matrix.Reset(); _matrix.RotateAt(_currentRotateAngle, _rectCenterPoint); // 使用更新后的旋转角度 line_path.Transform(_matrix); } if (_regionVertex.Length != _points.Count) { _regionVertex = new Region[_points.Count]; } for (int i = 0; i < _points.Count; i++) { DrawVertex1(vrtx_path, i); } vrtx_path.Transform(_matrix); } break; case ShapeTypeEnum.Circle: { if (_points.Count == 2) { float radius = PointHelper.Distance(ScalePoint(_points[0]), ScalePoint(_points[1])); line_path.AddEllipse( ScalePoint(_points[0]).X - radius, ScalePoint(_points[0]).Y - radius, radius * 2, radius * 2); } for (int i = 0; i < _points.Count; i++) { DrawVertex(vrtx_path, i); } } break; case ShapeTypeEnum.LineStrip: { line_path.StartFigure(); line_path.AddLine(ScalePoint(_points[0]), ScalePoint(_points[0])); for (int i = 0; i < _points.Count; i++) { PointF pt = _points[i]; line_path.AddLine(ScalePoint(pt), ScalePoint(pt)); DrawVertex(vrtx_path, i); } } break; case ShapeTypeEnum.Line: { // 添加框线到路径 var tmpPoints = _points.Select(p => ScalePoint(p)).ToList(); line_path.AddLines(tmpPoints.ToArray()); if (IsDrawLineVirtualRect && tmpPoints.Count == 2) { var center = new PointF((tmpPoints[0].X + tmpPoints[1].X) / 2, (tmpPoints[0].Y + tmpPoints[1].Y) / 2); // 计算两点之间的角度 float dx = tmpPoints[1].X - tmpPoints[0].X; float dy = tmpPoints[1].Y - tmpPoints[0].Y; float distance = PointHelper.Distance(tmpPoints[0], tmpPoints[1]); double angle = Math.Atan2(dy, dx) * (180.0 / Math.PI); // 转换为度数 float l = center.X - distance / 2; float t = center.Y - LineVirtualRectWidth / 2; float r = center.X + distance / 2; float b = center.Y + LineVirtualRectWidth / 2; PointF ptLT = new PointF(l, t); PointF ptRT = new PointF(r, t); PointF ptRB = new PointF(r, b); PointF ptLB = new PointF(l, b); #if false RectangleF rect = new RectangleF(ptLT, new SizeF(distance, LineVirtualRectWidth)); GraphicsPath rectPath = new GraphicsPath(); rectPath.AddRectangle(rect); //// 设置矩阵以进行旋转和位移 Matrix matrix = new Matrix(); matrix.RotateAt((float)angle, center); // 旋转 // 应用变换 rectPath.Transform(matrix); // 画框线 painter.DrawPath(pen, rectPath); #else RectangleF rect = new RectangleF(ptLT, new SizeF(distance, LineVirtualRectWidth)); LineVirtualRectPoints = new PointF[4] { ptLT,ptRT,ptRB,ptLB }; //// 设置矩阵以进行旋转和位移 Matrix matrix = new Matrix(); matrix.RotateAt((float)angle, center); // 旋转 matrix.TransformPoints(LineVirtualRectPoints); GraphicsPath rectPath = new GraphicsPath(); rectPath.AddPolygon(LineVirtualRectPoints); Pen rectpen = new Pen(Color.FromArgb(60, 0, 255, 0), 1); // 画框线 painter.DrawPath(rectpen, rectPath); #endif } // 添加节点到路径 for (int i = 0; i < _points.Count; i++) { DrawVertex(vrtx_path, i); } if (IsClosed()) { line_path.AddLine(ScalePoint(_points[0]), ScalePoint(_points[0])); } } break; case ShapeTypeEnum.Polygon: case ShapeTypeEnum.Point: default: { // 添加多边形框线到路径 line_path.AddLines(_points.Select(p => ScalePoint(p)).ToArray()); // 添加节点到路径 for (int i = 0; i < _points.Count; i++) { DrawVertex(vrtx_path, i); } if (IsClosed()) { line_path.AddLine(ScalePoint(_points[0]), ScalePoint(_points[0])); } } break; } #region 将点绘制到画布上 // 画框线 painter.DrawPath(pen, line_path); // 填充节点 if (vrtx_path.PointCount > 0) { painter.DrawPath(pen, vrtx_path); painter.FillPath(new SolidBrush(vertex_fill_color), vrtx_path); } if (fill) // 是否填充多边形 { Color fillColor = Selected ? select_fill_color : fill_color; painter.FillPath(new SolidBrush(fillColor), line_path); } #endregion } private Region[] _regionVertex = new Region[] { }; private void DrawVertex1(GraphicsPath path, int i) { PointF pt = _points[i]; _regionVertex[i] = new Region(new RectangleF( pt.X - _epsilon, pt.Y - _epsilon, _epsilon * 2, _epsilon * 2)); // 将节点变换 PointF[] transformedPoint = new PointF[] { pt }; _matrix.TransformPoints(transformedPoint); // 变换节点位置 pt = transformedPoint[0]; // 获取变换后的节点位置 // 绘制节点 float d = point_size; // Point size PointTypeEnum shape = point_type; // Point shape PointF point = ScalePoint(pt); if (i == _highlightIndex) { var setting = _highlightSettings[_highlightMode]; var size = setting.PointSize; shape = setting.PointType; d *= size; // Example for highlighting } if (_highlightIndex >= 0) { _vertex_fill_color = hvertex_fill_color; } else { _vertex_fill_color = vertex_fill_color; } switch (shape) { case PointTypeEnum.SQUARE: path.AddRectangle(new RectangleF(point.X - d / 2, point.Y - d / 2, d, d)); break; case PointTypeEnum.ROUND: path.AddEllipse(point.X - d / 2, point.Y - d / 2, d, d); break; default: throw new InvalidOperationException("Unsupported vertex shape"); } } private void DrawVertex(GraphicsPath path, int i) { PointF pt = _points[i]; float d = point_size; // Point size PointTypeEnum shape = point_type; // Point shape PointF point = ScalePoint(pt); if (i == _highlightIndex) { var setting = _highlightSettings[_highlightMode]; var size = setting.PointSize; shape = setting.PointType; d *= size; // Example for highlighting } if (_highlightIndex >= 0) { _vertex_fill_color = hvertex_fill_color; } else { _vertex_fill_color = vertex_fill_color; } switch (shape) { case PointTypeEnum.SQUARE: path.AddRectangle(new RectangleF(point.X - d / 2, point.Y - d / 2, d, d)); break; case PointTypeEnum.ROUND: path.AddEllipse(point.X - d / 2, point.Y - d / 2, d, d); break; default: throw new InvalidOperationException("Unsupported vertex shape"); } } /// /// 查找离鼠标最近且距离小于阈值的节点 /// /// 鼠标位置 /// 阈值 /// 返回节点的索引 public int NearestVertex(PointF point, float epsilon = DFT_VTX_EPSILON) { switch (ShapeType) { case ShapeTypeEnum.Rectangle: { for (int i = 0; i < _regionVertex.Length; i++) { if (_regionVertex[i] == null) { break; } if (_regionVertex[i].IsVisible(point)) { return i; } } } break; default: { _epsilon = epsilon; float min_distance = float.MaxValue; int min_i = -1; PointF scaledPoint = new PointF(point.X * scale, point.Y * scale); for (int i = 0; i < _points.Count; i++) { // 缩放顶点 PointF scaledVertex = new PointF(_points[i].X * scale, _points[i].Y * scale); float dist = PointHelper.Distance(scaledVertex, scaledPoint); // 检查距离是否在 epsilon 范围内 if (dist <= epsilon && dist < min_distance) { min_distance = dist; min_i = i; } } return min_i; } } return -1; } public int NearestEdge(PointF point, float epsilon) { float min_distance = float.MaxValue; int post_i = -1; PointF scaledPoint = new PointF(point.X * scale, point.Y * scale); for (int i = 0; i < _points.Count; i++) { // 计算边的两个端点 PointF start = new PointF(this[i - 1].X * scale, this[i - 1].Y * scale); PointF end = new PointF(this[i].X * scale, this[i].Y * scale); // 计算到线段的距离 float dist = PointHelper.DistanceToLine(scaledPoint, start, end); // 检查距离是否在 epsilon 范围内 if (dist <= epsilon && dist < min_distance) { min_distance = dist; post_i = i; } } return post_i; } public bool ContainsPoint(PointF point) { return MakePath().IsVisible(point); } private GraphicsPath MakePath() { GraphicsPath path = new GraphicsPath(); if (ShapeType == ShapeTypeEnum.Rectangle) { if (_points.Count == 2) { // 创建矩形路径 RectangleF rect = new RectangleF( Math.Min(_points[0].X, _points[1].X), Math.Min(_points[0].Y, _points[1].Y), Math.Abs(_points[1].X - _points[0].X), Math.Abs(_points[1].Y - _points[0].Y)); path.AddRectangle(rect); } } else if (ShapeType == ShapeTypeEnum.Circle) { if (_points.Count == 2) { // 计算半径 float radius = PointHelper.Distance(_points[0], _points[1]); path.AddEllipse(_points[0].X - radius, _points[0].Y - radius, radius * 2, radius * 2); } } else { // 处理多边形 path.StartFigure(); path.AddLine(_points[0], _points[1]); for (int i = 2; i < _points.Count; i++) { path.AddLine(_points[i - 1], _points[i]); } path.CloseFigure(); // 结束图形 } return path; } public RectangleF BoundingRect() { return MakePath().GetBounds(); } public void MoveBy(PointF offset) { for (int i = 0; i < _points.Count; i++) { _points[i] = new PointF(_points[i].X + offset.X, _points[i].Y + offset.Y); } } /// /// 移动特定顶点 /// /// /// /// public void MoveVertexBy(int index, PointF offset) { if (index >= 0 && index < _points.Count) { _rectSelectedVertex = index; _rectSelectedMoveVertex = new PointF(_points[index].X, _points[index].Y); _points[index] = new PointF(_points[index].X + offset.X, _points[index].Y + offset.Y); } else { throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range."); } } public void HighlightVertex(int i, HighlightModeEnum action) { this._highlightIndex = i; this._highlightMode = action; } public void HighlightClear() { _highlightIndex = -1; } public FlyShape Copy() { var jsonStr = JsonConvert.SerializeObject(this); FlyShape copyShp = JsonConvert.DeserializeObject(jsonStr); return copyShp; } public int Length => _points.Count(); public PointF this[int index] { get { if (index == -1) { return _points[_points.Count - 1]; } return _points[index]; } set { if (index == -1) { _points[_points.Count - 1] = value; } else { _points[index] = value; } } } private void NomalizeRotateAngle() { if (_currentRotateAngle >= 360) { _currentRotateAngle %= 360; } else if (_currentRotateAngle < 0) { _currentRotateAngle = 360 - (-_currentRotateAngle % 360); } } } }