选择图像区域矩形框控件【原创】

1. 矩形框控件效果如何?

选择图像区域矩形框控件【原创】

  • 上下左右等8点可以拉伸
  • 鼠标滑轮支持缩放,矩形框边框等比例缩放
  • 选中矩形框左右拖拽
  • 返回矩形框区域对应的图片的X,Y坐标
  • 可同时支持多个矩形框

2. 矩形框使用方式?

  • 初始化
//矩形框控件添加背景图片 rockRectControl.BackImage = bitmap; //声明一个矩形框,传入左上角和右下角坐标 RockRectangle rect = new RockRectangle(); var p1 = item.DistinguishRegion.LeftTopCorner; var p2 = item.DistinguishRegion.RightBottomCorner; rect.Rectangle = Rectangle.FromLTRB((int)p1.X, (int)p1.Y, (int)p2.X, (int)p2.Y); //把矩形框添加到矩形框控件中,可以添加多个矩形 rockRectControl.RockRectangles.Add(rect); 
  • 获取矩形框区域对应的图片坐标
//找到矩形控件中某一个矩形框 Rectangle r = rockRectControl.RockRectangles[i].Rectangle; //直接读取即可 var rp = new RockRegion(); rp.LeftTopCorner.X = r.X; rp.LeftTopCorner.Y = r.Y; rp.RightBottomCorner.X = r.Right; rp.RightBottomCorner.Y = r.Bottom; 

3. 矩形框控件源码?

  • RockRectangle源码
using System.Drawing;  namespace NcModule.Tools;  [Serializable] public class RockRectangle {     private List<LittleRectangle> littleRectangles = new List<LittleRectangle>();     public Rectangle Rectangle { set; get; }      internal List<LittleRectangle> GetLittleRectangles()     {         littleRectangles.Clear();         littleRectangles.Add(new LittleRectangle(Rectangle, PosSizableRect.LeftUp));         littleRectangles.Add(new LittleRectangle(Rectangle, PosSizableRect.LeftMiddle));         littleRectangles.Add(new LittleRectangle(Rectangle, PosSizableRect.LeftBottom));         littleRectangles.Add(new LittleRectangle(Rectangle, PosSizableRect.BottomMiddle));         littleRectangles.Add(new LittleRectangle(Rectangle, PosSizableRect.RightUp));         littleRectangles.Add(new LittleRectangle(Rectangle, PosSizableRect.RightBottom));         littleRectangles.Add(new LittleRectangle(Rectangle, PosSizableRect.RightMiddle));         littleRectangles.Add(new LittleRectangle(Rectangle, PosSizableRect.UpMiddle));         return littleRectangles;     }      public double RotationAngle { set; get; } }  internal class LittleRectangle {     //小矩形的宽度     private int rectangleWidth = 8;      /// <summary>     /// 矩形放大的倍数     /// </summary>     public static double Enlarge = 1;      /// <summary>     /// 小矩形的位置     /// </summary>     public PosSizableRect Location { set; get; }      public Rectangle Rectangle { set; get; }      public LittleRectangle(Rectangle rect, PosSizableRect location)     {         this.Location = location;         switch (location)         {             case PosSizableRect.LeftUp:                 this.Rectangle = createRectSizableNode(rect.X, rect.Y); break;              case PosSizableRect.LeftMiddle:                 this.Rectangle = createRectSizableNode(rect.X, rect.Y + +rect.Height / 2); break;              case PosSizableRect.LeftBottom:                 this.Rectangle = createRectSizableNode(rect.X, rect.Y + rect.Height); break;              case PosSizableRect.BottomMiddle:                 this.Rectangle = createRectSizableNode(rect.X + rect.Width / 2, rect.Y + rect.Height); break;              case PosSizableRect.RightUp:                 this.Rectangle = createRectSizableNode(rect.X + rect.Width, rect.Y); break;              case PosSizableRect.RightBottom:                 this.Rectangle = createRectSizableNode(rect.X + rect.Width, rect.Y + rect.Height); break;              case PosSizableRect.RightMiddle:                 this.Rectangle = createRectSizableNode(rect.X + rect.Width, rect.Y + rect.Height / 2); break;              case PosSizableRect.UpMiddle:                 this.Rectangle = createRectSizableNode(rect.X + rect.Width / 2, rect.Y); break;             default:                 this.Rectangle = new Rectangle(); break;         }     }      private Rectangle createRectSizableNode(int x, int y)     {         int rectWidth = (int)(rectangleWidth * Enlarge);         if (rectWidth < rectangleWidth)         {             Enlarge = 1;             rectWidth = rectangleWidth;         }         return new Rectangle(x - rectWidth / 2, y - rectWidth / 2, rectWidth, rectWidth);     } }  internal enum PosSizableRect {     UpMiddle,     LeftMiddle,     LeftBottom,     LeftUp,     RightUp,     RightMiddle,     RightBottom,     BottomMiddle,     None }; 
  • RockRectControl源码
using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Windows.Forms;  namespace NcModule.Tools;  public partial class RockRectControl : UserControl {     private Color borderColor = Color.Green;     private float borderWidth = 2;     private float defaultFontSize = 16;      private List<RockRectangle> rockRectangles = new List<RockRectangle>();      //是否显示序号     private bool isPrintNum = true;      private Font font = new Font("宋体", 16, FontStyle.Bold);      //背景图片     private Image backImage = default!;      //图片有效区域     private Rectangle effectiveRect = default(Rectangle);      //缩放比例,用double多次运算后会失真,故用百分比     private int zoomScale = 100;//图片本身的缩放比例      private int oldZoomScale = 100;     private int zoomMinScale = 60;     private int zoomMaxScale = 500;     private int stepScale = 20;//每次缩放比例     private Bitmap cloneBackImage = default!;     private double imageScale;//真实图片与显示是的缩放比例     private Point realImageCorePoint = new Point();//真实图片的中心点坐标偏移量,当放大或拖拽 时中心点发生变更     private Point wheelPoint = new Point();//滚动时的坐标     private bool zoomScaleIsUpdate = true;      public Image BackImage     {         set         {             this.backImage = value;             if (this.backImage != null)             {                 //黑白图,故格式用Format16bppRgb555,可以降低内存                 cloneBackImage = new Bitmap(this.backImage.Width, this.backImage.Height, PixelFormat.Format16bppRgb555);             }         }         get { return this.backImage; }     }      /// <summary>     /// 矩形框的颜色     /// </summary>     public Color BorderColor     {         set { this.borderColor = value; }         get { return this.borderColor; }     }      /// <summary>     /// 矩形框边框的粗细     /// </summary>     public float BorderWidth     {         set { this.borderWidth = value; }         get { return this.borderWidth; }     }      public List<RockRectangle> RockRectangles     {         get { return this.rockRectangles; }     }      public RockRectControl()     {         InitializeComponent();         this.init();     }      private void init()     {         //双缓冲         this.DoubleBuffered = true;     }      private void setFitImageRect()     {         if (this.cloneBackImage == null)         {             return;         }         double imageAspect = this.cloneBackImage.Width * 1.0 / this.cloneBackImage.Height;         double controlAspect = this.Width * 1.0 / this.Height;         //以高为主         if (imageAspect < controlAspect)         {             double imageHeight = this.Height;             double imageWidth = imageHeight * imageAspect;             int x = (int)((this.Width - imageWidth) / 2);             this.effectiveRect = new Rectangle(x, 0, (int)imageWidth, (int)imageHeight);         }         else         {             //以宽为主             double imageWidth = this.Width;             double imageHeight = imageWidth / imageAspect;             int y = (int)((this.Height - imageHeight) / 2);             this.effectiveRect = new Rectangle(0, y, (int)imageWidth, (int)imageHeight);         }         this.imageScale = this.cloneBackImage.Width * 1.0 / this.effectiveRect.Width;         //放大的最大值,只能放大到图片本来的大小         this.zoomMaxScale = (int)Math.Round(imageScale * 100);     }      //记录移动前鼠标的位置     private int oldCursorX, oldCursorY;      private int selectRectIndex = -1;     private PosSizableRect selectLocation = PosSizableRect.None;      protected override void OnMouseDown(MouseEventArgs e)     {         if (e.Button == MouseButtons.Left)         {             if (this.effectiveRect.Contains(e.Location))             {                 Point imageP = this.localPoint2ImagePoint(e.Location);                 this.oldCursorX = imageP.X;                 this.oldCursorY = imageP.Y;                 this.changeCursor(imageP, true);                 if (selectLocation != PosSizableRect.None)                 {                     return;                 }                 //判断当前位置是在哪个矩形内                 foreach (var item in this.RockRectangles)                 {                     if (this.isInRect(imageP, item.Rectangle, item.RotationAngle))                     {                         this.selectRectIndex = this.RockRectangles.IndexOf(item);                         return;                     }                 }             }         }         selectRectIndex = -1;     }      protected override void OnMouseUp(MouseEventArgs e)     {         selectRectIndex = -1;         selectLocation = PosSizableRect.None;         this.Invalidate();     }      protected override void OnMouseMove(MouseEventArgs le)     {         if (le.Button == MouseButtons.Left)         {             if (this.selectRectIndex != -1)             {                 Rectangle rect = this.RockRectangles[this.selectRectIndex].Rectangle;                 Point e = this.localPoint2ImagePoint(le.Location);                 switch (selectLocation)                 {                     case PosSizableRect.LeftUp:                         rect.X += e.X - oldCursorX;                         rect.Width -= e.X - oldCursorX;                         rect.Y += e.Y - oldCursorY;                         rect.Height -= e.Y - oldCursorY;                         break;                      case PosSizableRect.LeftMiddle:                         rect.X += e.X - oldCursorX;                         rect.Width -= e.X - oldCursorX;                         break;                      case PosSizableRect.LeftBottom:                         rect.Width -= e.X - oldCursorX;                         rect.X += e.X - oldCursorX;                         rect.Height += e.Y - oldCursorY;                         break;                      case PosSizableRect.BottomMiddle:                         rect.Height += e.Y - oldCursorY;                         break;                      case PosSizableRect.RightUp:                         rect.Width += e.X - oldCursorX;                         rect.Y += e.Y - oldCursorY;                         rect.Height -= e.Y - oldCursorY;                         break;                      case PosSizableRect.RightBottom:                         rect.Width += e.X - oldCursorX;                         rect.Height += e.Y - oldCursorY;                         break;                      case PosSizableRect.RightMiddle:                         rect.Width += e.X - oldCursorX;                         break;                      case PosSizableRect.UpMiddle:                         rect.Y += e.Y - oldCursorY;                         rect.Height -= e.Y - oldCursorY;                         break;                      default:                         rect.X = rect.X + e.X - this.oldCursorX;                         rect.Y = rect.Y + e.Y - this.oldCursorY;                         break;                 }                 this.RockRectangles[this.selectRectIndex].Rectangle = rect;                 this.oldCursorX = e.X;                 this.oldCursorY = e.Y;                 Invalidate();             }         }         else         {             if (this.effectiveRect.Contains(le.Location))             {                 this.changeCursor(this.localPoint2ImagePoint(le.Location));             }             else             {                 this.Cursor = Cursors.Default;             }         }     }      protected override void OnMouseWheel(MouseEventArgs e)     {         this.wheelPoint = this.localPoint2ImagePoint(e.Location);         if (e.Delta > 0)//上滚放大         {             if (this.zoomScale < this.zoomMaxScale)             {                 this.stepScale = Math.Abs(this.stepScale);                 this.zoomScale += this.stepScale;             }         }         else         {             //下滚缩小             if (this.zoomScale > this.zoomMinScale)             {                 this.stepScale = -Math.Abs(this.stepScale);                 this.zoomScale += this.stepScale;             }         }         this.Invalidate();     }      protected override void OnSizeChanged(EventArgs e)     {         this.Invalidate();     }      protected override void OnPaint(PaintEventArgs pe)     {         //背景图片存在才绘制         if (this.cloneBackImage != null)         {             this.setFitImageRect();             //把矩形画在背景图片上             this.paintRect();             //把图片绘制到界面上             this.paintImageToControl(pe.Graphics);         }     }      //在背景图片上画框     private void paintRect()     {         var g = Graphics.FromImage(cloneBackImage);         //画背景图         g.DrawImage(this.backImage, 0, 0, cloneBackImage.Width, cloneBackImage.Height);         //画的线平滑         //g.InterpolationMode = InterpolationMode.Low;         //设置高质量,低速度呈现平滑程度         //g.SmoothingMode = SmoothingMode.HighSpeed;         g.CompositingQuality = CompositingQuality.AssumeLinear;         //在图像上矩形         using (var path = new GraphicsPath())         {             foreach (var item in this.RockRectangles)             {                 //动态加粗线条                 double enlarge = this.imageScale * 100 / this.zoomScale;                 float nBorderWidth = (float)(this.borderWidth * enlarge);                 if (nBorderWidth < this.borderWidth)                 {                     nBorderWidth = this.borderWidth;                 }                 LittleRectangle.Enlarge = enlarge;                 this.font = new Font("宋体", (float)(this.defaultFontSize * enlarge), FontStyle.Bold);                 path.Reset();                 this.getPath(path, item.Rectangle, item.RotationAngle);                 g.DrawPath(new Pen(this.borderColor, nBorderWidth), path);                 //写序号                 if (this.isPrintNum)                 {                     string num = (this.RockRectangles.IndexOf(item) + 1).ToString();                     g.DrawString(num, this.font, new SolidBrush(this.borderColor), this.getCenter(item.Rectangle));                 }                 //画每个大矩形里面的8个小矩形                 //获取8个小矩形                 var littleRects = item.GetLittleRectangles();                 Rectangle rect = item.Rectangle;                 Point center = new Point(rect.X + rect.Width / 2, rect.Y + rect.Height / 2);                  foreach (var littleRect in littleRects)                 {                     path.Reset();                     this.getPath(path, littleRect.Rectangle, item.RotationAngle, center);                     g.DrawPath(new Pen(this.borderColor, nBorderWidth), path);                 }             }         }         g.Dispose();     }      //把图片绘制到控件上     private void paintImageToControl(Graphics g)     {         //设置高质量插值法         g.InterpolationMode = InterpolationMode.High;         //设置高质量,低速度呈现平滑程度         g.SmoothingMode = SmoothingMode.HighQuality;         g.CompositingQuality = CompositingQuality.GammaCorrected;         //获取图片的的区域         int width = (int)(cloneBackImage.Width * 100 / zoomScale);         int height = (int)(cloneBackImage.Height * 100 / zoomScale);          //此时是以中心点来缩放的,如果以滑轮中心缩放,则需要知道实际图片的width和height的缩放比例         //原理是放大后,鼠标相对于控件坐标不变,鼠标向对于图像坐标也不变         //realImageCorePoint在反复计算时有极少误差,所以当zoomScale不变化是,不更新realImageCorePoint         if (zoomScale != 100)         {             if (this.oldZoomScale != this.zoomScale)             {                 realImageCorePoint.X = (int)Math.Round((this.stepScale * wheelPoint.X + (this.zoomScale - this.stepScale) * realImageCorePoint.X) * 1.0 / this.zoomScale);                 realImageCorePoint.Y = (int)Math.Round((this.stepScale * wheelPoint.Y + (this.zoomScale - this.stepScale) * realImageCorePoint.Y) * 1.0 / this.zoomScale);                 this.oldZoomScale = this.zoomScale;                 zoomScaleIsUpdate = true;             }             else             {                 //当放大停止后,需要重新刷新一次                 if (zoomScaleIsUpdate)                 {                     this.Invalidate();                     zoomScaleIsUpdate = false;                 }             }         }         else         {             realImageCorePoint.X = (int)((cloneBackImage.Width - width) / 2.0);             realImageCorePoint.Y = (int)((cloneBackImage.Height - height) / 2.0);         }         Rectangle srcRect = new Rectangle(realImageCorePoint.X, realImageCorePoint.Y, width, height);         g.DrawImage(cloneBackImage, this.effectiveRect, srcRect, GraphicsUnit.Pixel);     }      //控件中的点与元素图像的点转换     private Point localPoint2ImagePoint(Point p)     {         p.X = (int)((p.X - (this.Width - this.effectiveRect.Width) / 2.0) * imageScale * 100 / zoomScale) + realImageCorePoint.X;         p.Y = (int)((p.Y - (this.Height - this.effectiveRect.Height) / 2.0) * imageScale * 100 / zoomScale) + realImageCorePoint.Y;         return p;     }      private bool isInRect(Point p, Rectangle rect, double angle)     {         Point centerP = this.getCenter(rect);         //获取反旋转后的点         return this.isInRect(p, rect, angle, centerP);     }      /// <summary>     /// 判断某个点是否在矩形内     /// </summary>     /// <param name="p"></param>     /// <param name="rect"></param>     /// <param name="angle"></param>     /// <param name="centerP"></param>     /// <returns></returns>     private bool isInRect(Point p, Rectangle rect, double angle, Point centerP)     {         //获取反旋转后的点         Point rotateP = this.getRotatePoint(p, -angle, centerP);         return rect.Contains(rotateP);     }      /// <summary>     /// 改变鼠标的图标     /// </summary>     /// <param name="p"></param>     private void changeCursor(Point p, bool updateSelectData = false)     {         bool isInBigRect = false;         foreach (var item in this.RockRectangles)         {             if (this.isInRect(p, item.Rectangle, item.RotationAngle))             {                 isInBigRect = true;             }             foreach (var littleRect in item.GetLittleRectangles())             {                 //如果图标在小矩形内                 if (this.isInRect(p, littleRect.Rectangle, item.RotationAngle, this.getCenter(item.Rectangle)))                 {                     this.Cursor = this.getCursor(littleRect.Location);                     if (updateSelectData)                     {                         this.selectRectIndex = this.RockRectangles.IndexOf(item);                         this.selectLocation = littleRect.Location;                     }                     return;                 }             }         }         if (isInBigRect)         {             this.Cursor = Cursors.SizeAll;         }         else         {             this.Cursor = Cursors.Default;         }     }      private Cursor getCursor(PosSizableRect p)     {         switch (p)         {             case PosSizableRect.LeftUp:                 return Cursors.SizeNWSE;              case PosSizableRect.LeftMiddle:                 return Cursors.SizeWE;              case PosSizableRect.LeftBottom:                 return Cursors.SizeNESW;              case PosSizableRect.BottomMiddle:                 return Cursors.SizeNS;              case PosSizableRect.RightUp:                 return Cursors.SizeNESW;              case PosSizableRect.RightBottom:                 return Cursors.SizeNWSE;              case PosSizableRect.RightMiddle:                 return Cursors.SizeWE;              case PosSizableRect.UpMiddle:                 return Cursors.SizeNS;              default:                 return Cursors.Default;         }     }      //获取矩形中心     private Point getCenter(Rectangle rect)     {         return new Point(rect.X + rect.Width / 2, rect.Y + rect.Height / 2);     }      /// <summary>     /// 获取矩形旋转后的路径     /// </summary>     /// <param name="rectangle"></param>     /// <param name="angle"></param>     private void getPath(GraphicsPath path, Rectangle rect, double angle)     {         Point center = this.getCenter(rect);         this.getPath(path, rect, angle, center);     }      private void getPath(GraphicsPath path, Rectangle rect, double angle, Point center)     {         path.AddRectangle(rect);         var a = -angle * (Math.PI / 180);         var n1 = (float)Math.Cos(a);         var n2 = (float)Math.Sin(a);         var n3 = -(float)Math.Sin(a);         var n4 = (float)Math.Cos(a);         var n5 = (float)(center.X * (1 - Math.Cos(a)) + center.Y * Math.Sin(a));         var n6 = (float)(center.Y * (1 - Math.Cos(a)) - center.X * Math.Sin(a));         Matrix matrix = new Matrix(n1, n2, n3, n4, n5, n6);         path.Transform(matrix);     }      //p1绕center旋转angle角度后点位     private Point getRotatePoint(Point p1, double angle, Point center)     {         //使用旋转矩阵求值         System.Windows.Media.RotateTransform rotateTransform = new System.Windows.Media.RotateTransform(angle, center.X, center.Y);         System.Windows.Point p = new System.Windows.Point(p1.X, p1.Y);         System.Windows.Point p2 = rotateTransform.Transform(p);         Point result = new Point();         result.X = (int)p2.X;         result.Y = (int)p2.Y;         return result;     } } 

4. 矩形框控件不足?

  • 目前矩形框控件不支持对背景图片的拖拽(本项目中未涉及此场景,后续可能会增加此功能)
  • 目前矩形框控件不支持旋转(源码中有旋转矩形框展示代码,但交互上没有实现,需要人为赋值旋转角度,后续可能会优化)
发表评论

相关文章