From 113fef2094a4f0942ddd9935266c4fed32cb7a2a Mon Sep 17 00:00:00 2001 From: zqm Date: Thu, 30 Oct 2025 09:56:02 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=96=B0=E7=9A=84=E5=9B=BE?= =?UTF-8?q?=E5=83=8F=E6=9B=B4=E6=96=B0=E9=80=BB=E8=BE=91=EF=BC=9A=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0LastImage=E5=92=8CInfoImage=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CS/Framework4.0/Toprie/Toprie/Camera.cs | 532 ++++++++++-------- 1 file changed, 288 insertions(+), 244 deletions(-) diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/Camera.cs b/Windows/CS/Framework4.0/Toprie/Toprie/Camera.cs index 1307f32..08b3a55 100644 --- a/Windows/CS/Framework4.0/Toprie/Toprie/Camera.cs +++ b/Windows/CS/Framework4.0/Toprie/Toprie/Camera.cs @@ -19,7 +19,25 @@ namespace JoyD.Windows.CS.Toprie // 是否正在接收图像 private bool _isReceivingImage = false; - + // 全局图像缓冲区bitmap,用于在其上绘制图像和mask信息 + private Bitmap _imageBuffer = null; + private const int BUFFER_WIDTH = 512; + private const int BUFFER_HEIGHT = 384; + + // 最后接收的图像 + private Image _lastImage = null; + + // 信息图像,用于显示额外信息 + private Image _infoImage = null; + + // 用于保护_lastImage的线程锁 + private readonly object _lastImageLock = new object(); + + // 用于保护_infoImage的线程锁 + private readonly object _infoImageLock = new object(); + + // 是否显示信息图像 + private bool _isDisplayingInfo = false; // 项目路径,用于数据文件的存取 private string _projectPath = ""; @@ -43,10 +61,10 @@ namespace JoyD.Windows.CS.Toprie if (_deviceManager != null) { _deviceManager.ProjectPath = _projectPath; - } - } + } + } + } } - } // 显示错误的定时器 private System.Windows.Forms.Timer _errorDisplayTimer; @@ -93,8 +111,8 @@ namespace JoyD.Windows.CS.Toprie { Console.WriteLine($"处理暂停/恢复图像更新时出错: {ex.Message}"); } - } - + } + public Camera() { InitializeComponent(); @@ -105,6 +123,9 @@ namespace JoyD.Windows.CS.Toprie // 将设计模式状态传递给DeviceManager UpdateDesignModeStatus(); + // 初始化图像缓冲区 + InitializeImageBuffer(); + // 只有在非设计模式下才初始化设备管理器和错误定时器 if (!DesignMode) { @@ -180,7 +201,32 @@ namespace JoyD.Windows.CS.Toprie _deviceManager.ConnectionException += DeviceManager_ConnectionException; } } - + + /// + /// 初始化图像缓冲区 + /// + private void InitializeImageBuffer() + { + try + { + // 创建512*384大小的bitmap作为图像缓冲区 + _imageBuffer = new Bitmap(BUFFER_WIDTH, BUFFER_HEIGHT); + Console.WriteLine($"图像缓冲区已初始化: {BUFFER_WIDTH}x{BUFFER_HEIGHT}"); + + // 初始化缓冲区为黑色背景 + using (Graphics g = Graphics.FromImage(_imageBuffer)) + { + g.Clear(Color.Black); + } + } + catch (Exception ex) + { + Console.WriteLine($"初始化图像缓冲区失败: {ex.Message}"); + // 如果初始化失败,确保_imageBuffer为null + _imageBuffer = null; + } + } + /// /// 初始化错误显示定时器 /// @@ -329,7 +375,7 @@ namespace JoyD.Windows.CS.Toprie if (pauseImageUpdateToolStripMenuItem.Text == "恢复图像更新") return; if (DesignMode) return; - Image image = null; + try { if (e.ImageData != null && e.ImageData.Length > 0) @@ -344,306 +390,253 @@ namespace JoyD.Windows.CS.Toprie using (Image tempImage = System.Drawing.Image.FromStream(ms)) { // 创建一个全新的位图而不仅仅是克隆,确保数据完整性 - image = new Bitmap(tempImage); + Image newImage = new Bitmap(tempImage); // 立即验证新创建的图像是否有效 try { // 访问Width和Height属性来验证图像是否有效 - int width = image.Width; - int height = image.Height; + int width = newImage.Width; + int height = newImage.Height; if (width <= 0 || height <= 0) { Console.WriteLine("创建的图像尺寸无效"); - image.Dispose(); - image = null; + newImage.Dispose(); + return; } } catch (Exception) { Console.WriteLine("创建的图像无效"); - image.Dispose(); - image = null; + newImage.Dispose(); + return; } - } - } - } - } - } - catch (Exception ex) - { - Console.WriteLine($"创建图像失败: {ex.Message}"); - // 确保在异常情况下释放资源 - if (image != null) - { - image.Dispose(); - image = null; - } - } - - if (image == null) - { - Console.WriteLine("接收到空或无效的图像数据"); - return; - } - - try - { - // 尝试创建图像的克隆以传递给UI线程 - Image clonedImage = null; - try - { - clonedImage = (Image)image.Clone(); - - // 验证克隆的图像是否有效 - if (clonedImage.Width <= 0 || clonedImage.Height <= 0) - { - Console.WriteLine("克隆的图像尺寸无效"); - clonedImage.Dispose(); - image.Dispose(); // 确保原始图像也被释放 - return; - } - } - catch (Exception ex) - { - Console.WriteLine($"克隆图像失败: {ex.Message}"); - image.Dispose(); // 确保原始图像被释放 - return; - } - - // 确保在UI线程上更新,并且检查控件是否已被释放 - if (!this.IsDisposed && !this.Disposing) - { - if (this.InvokeRequired) - { - try - { - // 创建本地副本以避免闭包问题 - Image imageForUI = clonedImage; - clonedImage = null; // 防止在异步操作期间被修改 - - // 使用BeginInvoke在UI线程上更新图像,避免可能的死锁问题 - this.BeginInvoke(new Action(() => - { - try + + // 按照用户要求:收到图像数据后,将图像保存到LastImage中,调用更新 + lock (_lastImageLock) { - UpdateImageOnUI(imageForUI); - } - catch (Exception ex) - { - Console.WriteLine($"更新UI图像失败: {ex.Message}"); - // 如果UI更新失败,确保克隆的图像被释放 - if (imageForUI != null) + // 释放旧的LastImage资源 + if (_lastImage != null) { - try - { - imageForUI.Dispose(); - } - catch {} + try { _lastImage.Dispose(); } catch {} } + // 设置新的LastImage + _lastImage = newImage; } - })); - } - catch (Exception) - { - // 异常情况下确保释放图像资源 - if (clonedImage != null) - { - try + + // 调用更新UI + this.BeginInvoke(new Action(() => { - clonedImage.Dispose(); - } - catch {} - clonedImage = null; + try + { + UpdateImageOnUI(); + } + catch (Exception ex) + { + Console.WriteLine($"更新UI图像失败: {ex.Message}"); + } + })); } - throw; } } - else - { - // 在UI线程上直接更新图像 - UpdateImageOnUI(clonedImage); - } } - else - { - // 如果控件已释放,确保释放图像资源 - clonedImage.Dispose(); - } - - // 释放原始图像资源 - image.Dispose(); } catch (Exception ex) { - Console.WriteLine($"处理图像接收事件失败: {ex.Message}"); - // 确保在任何异常情况下都释放原始图像 - if (image != null) - { - try - { - image.Dispose(); - } - catch {} - } + Console.WriteLine($"处理接收到的图像时出错: {ex.Message}"); } } /// /// 在UI线程上更新图像 /// - private void UpdateImageOnUI(Image image) - { + private void UpdateImageOnUI(Image image) // 保留原有方法签名,处理旧调用 + { + UpdateImageOnUI(); + } + + /// + /// 在UI线程上更新图像 - 新实现,按照用户要求: + /// 1. 先将LastImage绘制到全局缓冲 + /// 2. 再将InfoImage绘制到缓冲 + /// 3. 最后一次性绘制到图像框的bitmap + /// + private void UpdateImageOnUI() + { if (DesignMode) return; + // 线程安全检查 - 确保在UI线程上执行 if (this.InvokeRequired) - { + { try - { - this.BeginInvoke(new Action(UpdateImageOnUI), image); + { + this.BeginInvoke(new Action(UpdateImageOnUI)); } catch (ObjectDisposedException) - { - // 如果控件已被释放,确保释放图像资源 - if (image != null) - { - try { image.Dispose(); } catch {} - } + { Console.WriteLine("控件已释放,跳过UI更新"); } return; } - // 空值检查 - if (image == null) - { - Console.WriteLine("传入UpdateImageOnUI的图像为空"); - return; - } - // 连接状态检查 - 只在设备实际连接时处理图像 - if (_deviceManager.ConnectionStatus != ConnectionStatus.Connected) - { - Console.WriteLine("设备未连接,跳过图像更新"); - try { image.Dispose(); } catch {} - return; - } - // 增强图像有效性检查 - bool isImageValid = false; - Image safeImage = null; + Image lastImage = null; + Image infoImage = null; + Image oldImage = null; try - { - // 创建一个安全的图像副本,确保原图像不被破坏 - safeImage = new Bitmap(image); + { + // 连接状态检查 - 只在设备实际连接时处理图像 + if (_deviceManager.ConnectionStatus != ConnectionStatus.Connected) + { + Console.WriteLine("设备未连接,跳过图像更新"); + return; + } - // 预先验证图像是否有效 - if (safeImage.Width > 0 && safeImage.Height > 0) - { - // 尝试访问图像的像素数据,这是更严格的验证方式 - using (Graphics g = Graphics.FromImage(new Bitmap(1, 1))) - { - // 简单绘制操作验证图像是否可绘制 - g.DrawImage(safeImage, 0, 0, 1, 1); + // 检查图像缓冲区是否有效 + if (_imageBuffer == null) + { + Console.WriteLine("图像缓冲区未初始化,尝试重新初始化"); + InitializeImageBuffer(); + if (_imageBuffer == null) + { + Console.WriteLine("重新初始化图像缓冲区失败"); + return; } - isImageValid = true; - Console.WriteLine($"图像验证成功: {safeImage.Width}x{safeImage.Height}"); } - else - { - Console.WriteLine($"图像尺寸无效: {safeImage.Width}x{safeImage.Height}"); - } - } - catch (Exception ex) - { - Console.WriteLine($"图像验证失败: {ex.Message}"); - // 释放临时创建的安全图像 - if (safeImage != null) - { - try { safeImage.Dispose(); } catch {} - } - // 释放原始图像 - try { image.Dispose(); } catch {} - return; - } - - try - { + // 更新图像前先检查控件是否存在/已释放 if (this.IsDisposed || imageBox == null || imageBox.IsDisposed) - { - // 如果控件已释放,确保图像也被释放 - try { safeImage.Dispose(); } catch {} - try { image.Dispose(); } catch {} + { Console.WriteLine("控件已释放,无法更新图像"); return; } // 保存旧图像引用,以便在设置新图像后释放 - Image oldImage = imageBox.Image; + oldImage = imageBox.Image; - try - { - // 确保控件仍处于有效状态 - if (imageBox.IsDisposed || !isImageValid || safeImage == null) - { - try { safeImage.Dispose(); } catch {} - try { image.Dispose(); } catch {} - return; - } - - // 尝试设置新图像(使用验证过的安全副本) - imageBox.Image = safeImage; - - // 验证图像是否成功设置 - if (imageBox.Image != safeImage) - { - Console.WriteLine("图像设置失败,可能参数无效"); - try { safeImage.Dispose(); } catch {} - try { image.Dispose(); } catch {} - return; - } - - // 释放原始图像,因为我们现在使用的是安全副本 - try { image.Dispose(); } catch {} - - // 仅在新图像成功设置后释放旧图像 - if (oldImage != null && oldImage != safeImage) // 防止自引用释放 - { - try - { - oldImage.Dispose(); - } - catch {} + // 获取当前的LastImage引用 + lock (_lastImageLock) + { + if (_lastImage != null) + { + lastImage = (Image)_lastImage.Clone(); } } - catch (ArgumentException ex) when (ex.Message.Contains("参数无效")) - { - // 特别处理"参数无效"异常 - Console.WriteLine($"图像参数无效异常: {ex.Message}"); - try { safeImage.Dispose(); } catch {} - try { image.Dispose(); } catch {} - - // 尝试设置旧图像回来,如果可能的话 - if (oldImage != null) - { - try - { - imageBox.Image = oldImage; + + // 获取当前的InfoImage引用 + lock (_infoImageLock) + { + if (_infoImage != null) + { + infoImage = (Image)_infoImage.Clone(); + } + } + + // 在缓冲区上绘制图像 - 按照用户要求的步骤执行 + lock (_imageBuffer) + { + using (Graphics g = Graphics.FromImage(_imageBuffer)) + { + // 清除缓冲区背景为黑色 + g.Clear(Color.Black); + + // 步骤1:先将LastImage绘制到全局缓冲 + if (lastImage != null) + { + // 保持原始图像比例,居中显示 + float scale = Math.Min((float)BUFFER_WIDTH / lastImage.Width, (float)BUFFER_HEIGHT / lastImage.Height); + int scaledWidth = (int)(lastImage.Width * scale); + int scaledHeight = (int)(lastImage.Height * scale); + int x = (BUFFER_WIDTH - scaledWidth) / 2; + int y = (BUFFER_HEIGHT - scaledHeight) / 2; + + g.DrawImage(lastImage, x, y, scaledWidth, scaledHeight); } - catch - { - // 如果设置旧图像也失败,释放它 - try { oldImage.Dispose(); } catch {} + + // 步骤2:再将InfoImage绘制到缓冲 + if (infoImage != null) + { + g.DrawImage(infoImage, 0, 0); } + else if (_isDisplayingInfo) + { + // 如果没有InfoImage但需要显示信息,则绘制默认信息 + using (Font font = new Font("微软雅黑", 12, FontStyle.Bold)) + using (Brush textBrush = new SolidBrush(Color.Red)) + { + g.DrawString("测试信息", font, textBrush, 10, 10); + } + } + + // 检查是否需要绘制暂停状态的mask信息 + bool isPaused = pauseImageUpdateToolStripMenuItem.Text == "恢复图像更新"; + if (isPaused) + { + // 绘制半透明黑色背景 + using (Brush brush = new SolidBrush(Color.FromArgb(100, Color.Black))) + { + g.FillRectangle(brush, 0, 0, BUFFER_WIDTH, BUFFER_HEIGHT); + } + + // 绘制"暂停"文字 + using (Font font = new Font("微软雅黑", 48, FontStyle.Bold)) + using (Brush textBrush = new SolidBrush(Color.White)) + { + StringFormat format = new StringFormat(); + format.Alignment = StringAlignment.Center; + format.LineAlignment = StringAlignment.Center; + + g.DrawString("暂停", font, textBrush, + new Rectangle(0, 0, BUFFER_WIDTH, BUFFER_HEIGHT), format); + } + } + } + } + + // 步骤3:创建缓冲区的副本用于显示,避免在显示时锁定缓冲区 + Bitmap displayImage = null; + lock (_imageBuffer) + { + displayImage = (Bitmap)_imageBuffer.Clone(); + } + + // 将全局缓冲一次性绘制到图像框的bitmap + imageBox.Image = displayImage; + + if (lastImage != null) + { + Console.WriteLine($"图像更新成功: {lastImage.Width}x{lastImage.Height}"); + } + } + catch (ArgumentException ex) when (ex.Message.Contains("参数无效")) + { + // 特别处理"参数无效"异常 + Console.WriteLine($"图像参数无效异常: {ex.Message}"); + + // 尝试设置旧图像回来,如果可能的话 + if (oldImage != null && !this.IsDisposed && imageBox != null && !imageBox.IsDisposed) + { + try + { + imageBox.Image = oldImage; + } + catch + { + // 如果设置旧图像也失败,释放它 + try { oldImage.Dispose(); } catch {} } } } catch (Exception ex) - { + { Console.WriteLine($"更新图像UI异常: {ex.Message}"); - // 确保在任何异常情况下都释放所有图像资源 - try { safeImage.Dispose(); } catch {} - try { image.Dispose(); } catch {} + } + finally + { + // 确保在任何情况下都释放资源 + if (lastImage != null) { try { lastImage.Dispose(); } catch { } } + if (infoImage != null) { try { infoImage.Dispose(); } catch { } } + if (oldImage != null && oldImage != imageBox.Image) { try { oldImage.Dispose(); } catch { } } } } @@ -1383,11 +1376,62 @@ namespace JoyD.Windows.CS.Toprie // 无论是否在设计模式下,都需要释放图像资源 if (imageBox != null && !imageBox.IsDisposed && imageBox.Image != null) - { + { imageBox.Image.Dispose(); imageBox.Image = null; } + // 释放图像缓冲区资源 + if (_imageBuffer != null) + { + try + { + _imageBuffer.Dispose(); + _imageBuffer = null; + Console.WriteLine("图像缓冲区资源已释放"); + } + catch (Exception ex) + { + Console.WriteLine($"清理ImageBuffer资源异常: {ex.Message}"); + } + } + + // 释放LastImage资源 + lock (_lastImageLock) + { + if (_lastImage != null) + { + try + { + _lastImage.Dispose(); + _lastImage = null; + Console.WriteLine("LastImage资源已释放"); + } + catch (Exception ex) + { + Console.WriteLine($"清理LastImage资源异常: {ex.Message}"); + } + } + } + + // 释放InfoImage资源 + lock (_infoImageLock) + { + if (_infoImage != null) + { + try + { + _infoImage.Dispose(); + _infoImage = null; + Console.WriteLine("InfoImage资源已释放"); + } + catch (Exception ex) + { + Console.WriteLine($"清理InfoImage资源异常: {ex.Message}"); + } + } + } + // 释放组件资源 if (components != null) {