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


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

    test2.gif

    • 上下左右等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. 矩形框控件不足?

    • 目前矩形框控件不支持对背景图片的拖拽(本项目中未涉及此场景,后续可能会增加此功能)
    • 目前矩形框控件不支持旋转(源码中有旋转矩形框展示代码,但交互上没有实现,需要人为赋值旋转角度,后续可能会优化)
  • 相关阅读:
    分段控制器UISegmentedControl的使用、同一个控制器中实现多个View的切换、addChildViewController等方法的使用
    警示框UIAlertController的使用(看完马上会用!!)
    断言NSAssert的使用
    概念篇(一)
    《iOS开发进阶》书籍目录
    《编写高质量iOS与OS X代码的52个有效方法》书籍目录
    《精通Objective-C》书籍目录
    《iOS设计模式解析》书籍目录
    《精通iOS开发》书籍目录
    常用的代码块
  • 原文地址:https://www.cnblogs.com/Bonker/p/16877328.html
Copyright © 2020-2023  润新知