10.20PLC+相机2.3视觉修改

This commit is contained in:
17860779768
2025-08-25 16:33:58 +08:00
committed by Maikouce China
commit dca4b2afac
52 changed files with 11698 additions and 0 deletions

View 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();
}
}
}