10.20PLC+相机2.3视觉修改
This commit is contained in:
		
							
								
								
									
										381
									
								
								Check.Main/UI/ZoomPictureBox.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										381
									
								
								Check.Main/UI/ZoomPictureBox.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,381 @@ | ||||
| using OpenCvSharp; | ||||
| using OpenCvSharp.Extensions; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.ComponentModel; | ||||
| using System.Drawing; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| using System.Threading.Tasks; | ||||
| using System.Windows.Forms; | ||||
|  | ||||
| namespace Check.Main.UI | ||||
| { | ||||
|     public class ZoomPictureBox : PictureBox, IDisposable | ||||
|     { | ||||
|         private float zoom = 1.0f; | ||||
|         private System.Drawing.Point imageLocation; | ||||
|         private Image image; | ||||
|         private bool dragging = false; | ||||
|         private System.Drawing.Point mouseDownPos; | ||||
|         private System.Drawing.Point imageLocationOnMouseDown; | ||||
|  | ||||
|         // 矩形绘制相关 | ||||
|         private bool isDrawingRect = false;      // 绘制状态 | ||||
|         private Rectangle currentRect;           // 当前绘制矩形 | ||||
|         private Rectangle storedRect;            // 上次绘制并固定的矩形 | ||||
|         private bool hasStoredRect = false;      // 是否存在固定矩形 | ||||
|         private System.Drawing.Point rectStartPoint; | ||||
|  | ||||
|         // 一个私有的锁对象,专门用于保护对 'image' 字段的访问。 | ||||
|         private readonly object imageLock = new object(); | ||||
|  | ||||
|         private bool croppingEnabled = true; | ||||
|         //private bool croppingEnabled = true; | ||||
|         [Category("Behavior")] | ||||
|         [Description("是否允许通过右键绘制矩形裁剪区域。设置为 false 则不再响应右键并清除已有矩形。")] | ||||
|         public bool CroppingEnabled | ||||
|         { | ||||
|             get => croppingEnabled; | ||||
|             set | ||||
|             { | ||||
|                 croppingEnabled = value; | ||||
|                 // 禁用裁剪时清除所有矩形和裁剪结果 | ||||
|                 if (!croppingEnabled) | ||||
|                 { | ||||
|                     isDrawingRect = false; | ||||
|                     hasStoredRect = false; | ||||
|                     LastCroppedMat?.Dispose(); | ||||
|                     LastCroppedMat = null; | ||||
|                 } | ||||
|                 Invalidate(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // 裁剪完成事件,外部可订阅以接收 Mat | ||||
|         public event Action<Mat> Cropped; | ||||
|  | ||||
|         public Mat LastCroppedMat { get; private set; } | ||||
|  | ||||
|         private Color backgroundFillColor = Color.LightSteelBlue; | ||||
|         private Color rectangleColor = Color.Red; | ||||
|         private int rectangleThickness = 2; | ||||
|  | ||||
|         [Category("Appearance")] | ||||
|         [Description("指定控件背景的填充颜色。使用 BackColor 属性同步更新。")] | ||||
|         public Color BackgroundFillColor | ||||
|         { | ||||
|             get => backgroundFillColor; | ||||
|             set | ||||
|             { | ||||
|                 backgroundFillColor = value; | ||||
|                 base.BackColor = value;  // 同步 WinForms BackColor | ||||
|                 Invalidate(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|  | ||||
|         [Category("Appearance")] | ||||
|         [Description("绘制矩形框的颜色。")] | ||||
|         public Color RectangleColor { get => rectangleColor; set { rectangleColor = value; Invalidate(); } } | ||||
|  | ||||
|         [Category("Appearance")] | ||||
|         [Description("绘制矩形框的线宽。")] | ||||
|         public int RectangleThickness { get => rectangleThickness; set { rectangleThickness = Math.Max(1, value); Invalidate(); } } | ||||
|  | ||||
|         public ZoomPictureBox() | ||||
|         { | ||||
|             DoubleBuffered = true; | ||||
|             ResizeRedraw = true; | ||||
|             base.BackColor = backgroundFillColor; | ||||
|             SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true); | ||||
|             MouseWheel += ZoomPictureBox_MouseWheel; | ||||
|             MouseDown += ZoomPictureBox_MouseDown; | ||||
|             MouseMove += ZoomPictureBox_MouseMove; | ||||
|             MouseUp += ZoomPictureBox_MouseUp; | ||||
|             DoubleClick += ZoomPictureBox_DoubleClick; | ||||
|         } | ||||
|         // 重写背景绘制,使用自定义背景色 | ||||
|         protected override void OnPaintBackground(PaintEventArgs e) | ||||
|         { | ||||
|             e.Graphics.Clear(backgroundFillColor); | ||||
|         } | ||||
|         //[Browsable(false)] | ||||
|         //public new Image Image | ||||
|         //{ | ||||
|         //    get => image; | ||||
|         //    set | ||||
|         //    { | ||||
|         //        image = value; | ||||
|         //        zoom = 1.0f; | ||||
|         //        LastCroppedMat?.Dispose(); | ||||
|         //        LastCroppedMat = null; | ||||
|         //        isDrawingRect = false; | ||||
|         //        hasStoredRect = false; | ||||
|         //        FitImage(); | ||||
|         //        Invalidate(); | ||||
|         //    } | ||||
|         //} | ||||
|         // 【关键优化】 | ||||
|         // 2. 重写 Control.Dispose 方法来释放我们自己的资源 | ||||
|         protected override void Dispose(bool disposing) | ||||
|         { | ||||
|             if (disposing) | ||||
|             { | ||||
|                 // 在这里释放托管和非托管资源 | ||||
|                 // 清理事件订阅 (虽然在这个类里没有,但这是个好习惯) | ||||
|  | ||||
|                 // 释放我们持有的最后一个图像资源 | ||||
|                 lock (imageLock) | ||||
|                 { | ||||
|                     this.image?.Dispose(); | ||||
|                     this.image = null; | ||||
|                 } | ||||
|  | ||||
|                 // 释放我们持有的最后一个裁剪结果资源 | ||||
|                 this.LastCroppedMat?.Dispose(); | ||||
|                 this.LastCroppedMat = null; | ||||
|             } | ||||
|  | ||||
|             // 调用基类的 Dispose 方法来完成标准清理 | ||||
|             base.Dispose(disposing); | ||||
|         } | ||||
|  | ||||
|         // 重写 Image 属性,但不做任何额外操作,因为我们将通过一个新方法来更新它。 | ||||
|         [Browsable(false)] | ||||
|         public new Image Image | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 // 在访问时也加锁,确保读取的是一个完整的对象 | ||||
|                 lock (imageLock) | ||||
|                 { | ||||
|                     return image; | ||||
|                 } | ||||
|             } | ||||
|             // set 访问器可以保持原样,但我们不再直接使用它来更新图像 | ||||
|             private set | ||||
|             { | ||||
|                 lock (imageLock) | ||||
|                 { | ||||
|                     image = value; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         protected override void OnPaint(PaintEventArgs e) | ||||
|         { | ||||
|             base.OnPaint(e); | ||||
|             var g = e.Graphics; | ||||
|             //在绘制前获取锁,确保在绘制期间,image对象不会被其他线程替换或释放 | ||||
|             lock (imageLock) | ||||
|             { | ||||
|                 if (image != null) | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         int w = (int)(image.Width * zoom); | ||||
|                         int h = (int)(image.Height * zoom); | ||||
|                         g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; | ||||
|                         g.DrawImage(image, new Rectangle(imageLocation, new System.Drawing.Size(w, h))); | ||||
|                     } | ||||
|                     catch (Exception) | ||||
|                     { | ||||
|  | ||||
|                         // 如果在绘制时仍然发生罕见的GDI+错误,静默忽略,避免程序崩溃。 | ||||
|                         // 这通常意味着图像状态仍然存在问题,但UI不会因此卡死。 | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             //if (image != null) | ||||
|             //{ | ||||
|             //    int w = (int)(image.Width * zoom); | ||||
|             //    int h = (int)(image.Height * zoom); | ||||
|             //    g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; | ||||
|             //    g.DrawImage(image, new Rectangle(imageLocation, new System.Drawing.Size(w, h))); | ||||
|             //} | ||||
|             // 仅当允许裁剪时才绘制矩形 | ||||
|             if (croppingEnabled) | ||||
|             { | ||||
|                 using (var pen = new Pen(rectangleColor, rectangleThickness)) | ||||
|                 { | ||||
|                     if (hasStoredRect) g.DrawRectangle(pen, storedRect); | ||||
|                     else if (isDrawingRect) g.DrawRectangle(pen, currentRect); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 一个线程安全的、用于更新显示图像的公共方法。 | ||||
|         /// 这个方法将替换掉直接的 pictureBox.Image = ... 赋值。 | ||||
|         /// </summary> | ||||
|         /// <param name="newImage">要显示的新图像。此方法会接管该对象的所有权。</param> | ||||
|         public void SetImageThreadSafe(Image newImage) | ||||
|         { | ||||
|             Image oldImage = null; | ||||
|  | ||||
|             lock (imageLock) | ||||
|             { | ||||
|                 // 1. 保存旧图像的引用,以便在锁外部释放它 | ||||
|                 oldImage = this.image; | ||||
|                 // 2. 将新图像赋值给成员字段 | ||||
|                 this.image = newImage; | ||||
|             } | ||||
|             // 3. 【关键】在锁的外部释放旧图像,避免长时间持有锁 | ||||
|             oldImage?.Dispose(); | ||||
|  | ||||
|             // 4. 计算自适应并触发重绘 | ||||
|             FitImage(); | ||||
|             Invalidate(); | ||||
|         } | ||||
|  | ||||
|         private void FitImage() | ||||
|         { | ||||
|             //if (image == null) return; | ||||
|             //float sx = (float)ClientSize.Width / image.Width; | ||||
|             //float sy = (float)ClientSize.Height / image.Height; | ||||
|             //zoom = Math.Min(sx, sy); | ||||
|             //CenterImage(); | ||||
|             //在访问image属性前获取锁 | ||||
|             lock (imageLock) | ||||
|             { | ||||
|                 if (image == null) return; | ||||
|  | ||||
|                 try | ||||
|                 { | ||||
|                     float sx = (float)ClientSize.Width / image.Width; | ||||
|                     float sy = (float)ClientSize.Height / image.Height; | ||||
|                     zoom = Math.Min(sx, sy); | ||||
|                     CenterImage(); | ||||
|                 } | ||||
|                 catch (Exception) | ||||
|                 { | ||||
|  | ||||
|                     // 忽略错误,同 OnPaint | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void CenterImage() | ||||
|         { | ||||
|             if (image == null) return; | ||||
|             int w = (int)(image.Width * zoom); | ||||
|             int h = (int)(image.Height * zoom); | ||||
|             imageLocation = new System.Drawing.Point((ClientSize.Width - w) / 2, (ClientSize.Height - h) / 2); | ||||
|         } | ||||
|  | ||||
|         private void ZoomPictureBox_MouseWheel(object sender, MouseEventArgs e) | ||||
|         { | ||||
|             if (image == null) return; | ||||
|             float oldZoom = zoom; | ||||
|             zoom *= e.Delta > 0 ? 1.1f : 1 / 1.1f; | ||||
|             zoom = Math.Max(0.1f, Math.Min(zoom, 100f)); | ||||
|             var m = e.Location; | ||||
|             float ix = (m.X - imageLocation.X) / oldZoom; | ||||
|             float iy = (m.Y - imageLocation.Y) / oldZoom; | ||||
|             imageLocation = new System.Drawing.Point((int)(m.X - ix * zoom), (int)(m.Y - iy * zoom)); | ||||
|             Invalidate(); | ||||
|         } | ||||
|  | ||||
|         private void ZoomPictureBox_MouseDown(object sender, MouseEventArgs e) | ||||
|         { | ||||
|             if (image == null) return; | ||||
|             if (e.Button == MouseButtons.Left) | ||||
|             { | ||||
|                 hasStoredRect = false; | ||||
|                 LastCroppedMat?.Dispose(); | ||||
|                 LastCroppedMat = null; | ||||
|  | ||||
|                 dragging = true; | ||||
|                 mouseDownPos = e.Location; | ||||
|                 imageLocationOnMouseDown = imageLocation; | ||||
|                 Cursor = Cursors.Hand; | ||||
|             } | ||||
|             else if (e.Button == MouseButtons.Right && croppingEnabled) | ||||
|             { | ||||
|                 // 清除上次固定矩形和裁剪 | ||||
|                 hasStoredRect = false; | ||||
|                 LastCroppedMat?.Dispose(); | ||||
|                 LastCroppedMat = null; | ||||
|  | ||||
|                 // 开始新绘制 | ||||
|                 isDrawingRect = true; | ||||
|                 rectStartPoint = e.Location; | ||||
|                 currentRect = new Rectangle(e.Location, System.Drawing.Size.Empty); | ||||
|                 Invalidate(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void ZoomPictureBox_MouseMove(object sender, MouseEventArgs e) | ||||
|         { | ||||
|             if (dragging) | ||||
|             { | ||||
|                 var delta = new System.Drawing.Point(e.X - mouseDownPos.X, e.Y - mouseDownPos.Y); | ||||
|                 imageLocation = new System.Drawing.Point(imageLocationOnMouseDown.X + delta.X, imageLocationOnMouseDown.Y + delta.Y); | ||||
|                 Invalidate(); | ||||
|             } | ||||
|             else if (isDrawingRect) | ||||
|             { | ||||
|                 int x = Math.Min(rectStartPoint.X, e.X); | ||||
|                 int y = Math.Min(rectStartPoint.Y, e.Y); | ||||
|                 int w = Math.Abs(e.X - rectStartPoint.X); | ||||
|                 int h = Math.Abs(e.Y - rectStartPoint.Y); | ||||
|                 currentRect = new Rectangle(x, y, w, h); | ||||
|                 Invalidate(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void ZoomPictureBox_MouseUp(object sender, MouseEventArgs e) | ||||
|         { | ||||
|             if (e.Button == MouseButtons.Left && dragging) | ||||
|             { | ||||
|                 dragging = false; | ||||
|                 Cursor = Cursors.Default; | ||||
|             } | ||||
|             else if (e.Button == MouseButtons.Right && isDrawingRect) | ||||
|             { | ||||
|                 isDrawingRect = false; | ||||
|                 // 固定当前矩形框 | ||||
|                 storedRect = currentRect; | ||||
|                 hasStoredRect = true; | ||||
|                 if (croppingEnabled && image is Bitmap bmp) | ||||
|                 { | ||||
|                     this.LastCroppedMat?.Dispose(); | ||||
|                     this.LastCroppedMat = null; | ||||
|                     Mat srcMat = null; | ||||
|                     try | ||||
|                     { | ||||
|                         int ix = (int)((storedRect.X - imageLocation.X) / zoom); | ||||
|                         int iy = (int)((storedRect.Y - imageLocation.Y) / zoom); | ||||
|                         int iw = (int)(storedRect.Width / zoom); | ||||
|                         int ih = (int)(storedRect.Height / zoom); | ||||
|                         ix = Math.Max(0, ix); | ||||
|                         iy = Math.Max(0, iy); | ||||
|                         if (ix + iw > bmp.Width) iw = bmp.Width - ix; | ||||
|                         if (iy + ih > bmp.Height) ih = bmp.Height - iy; | ||||
|                         if (iw > 0 && ih > 0) | ||||
|                         { | ||||
|                             srcMat = BitmapConverter.ToMat(bmp); | ||||
|                             var roi = new Rect(ix, iy, iw, ih); | ||||
|                             LastCroppedMat = new Mat(srcMat, roi); | ||||
|                             Cropped?.Invoke(LastCroppedMat); | ||||
|                             bmp.Dispose(); | ||||
|                         } | ||||
|                     } | ||||
|                     finally | ||||
|                     { | ||||
|                         // 5.确保从Bitmap转换来的 srcMat 被释放 | ||||
|                         srcMat?.Dispose(); | ||||
|                     } | ||||
|                 } | ||||
|                 Invalidate(); | ||||
|             } | ||||
|         } | ||||
|         private void ZoomPictureBox_DoubleClick(object sender, EventArgs e) | ||||
|         { | ||||
|             if (image == null) return; | ||||
|             FitImage(); | ||||
|             Invalidate(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user