using Check.Main.Common;
using Check.Main.Infer;
using Check.Main.Result;
using Check.Main.UI;
using OpenCvSharp;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Forms;
namespace Check.Main.Camera
{
///
/// 静态全局相机管理器,负责所有相机的生命周期、配置应用和多相机同步
///
/// 222222
public static class CameraManager
{
//1、相机与UI管理----管理每台相机对象和对应的图像显示窗口。
public static Dictionary ActiveCameras { get; } = new Dictionary();
public static Dictionary OriginalImageDisplays { get; } = new Dictionary();//原始图像窗口
public static Dictionary ResultImageDisplays { get; } = new Dictionary();//结果图像窗口
//2、多相机同步逻辑
private static readonly Queue ProductQueue = new Queue();
private static readonly object QueueLock = new object();
private static int EnabledCameraCount = 0;
private static long _productCounter = 0;
private static readonly object _counterLock = new object();
// 3、--- 新增:硬触发模拟器 ---
private static readonly System.Timers.Timer _hardwareTriggerSimulator;
// 获取或设置模拟硬触发的间隔时间(毫秒)。
public static double TriggerInterval { get; set; } = 1000; // 默认1秒触发一次
// 获取一个值,该值指示硬件触发模拟器当前是否正在运行。
public static bool IsHardwareTriggerSimulating { get; private set; } = false;
///
/// 静态构造函数,用于一次性初始化静态资源。
///
static CameraManager()
{
//初始化硬触发模拟器
_hardwareTriggerSimulator = new System.Timers.Timer();
_hardwareTriggerSimulator.Elapsed += OnHardwareTriggerTimerElapsed;
_hardwareTriggerSimulator.AutoReset = true; // 确保定时器持续触发
_hardwareTriggerSimulator.Enabled = false; // 默认不启动
}
////********************初始化和启动流程*******************
/////
///// 准备所有相机硬件、UI窗口和后台处理器,但不开始采集。
///// 这是“启动设备”的第一阶段。
/////
//public static void PrepareAll(ProcessConfig config, FrmMain mainForm)//①准备所有相机和模型
//{
// // 1. 清理旧资源和UI
// mainForm.ClearStatusStrip();
// Shutdown();
// ThreadSafeLogger.Log("开始准备设备和模型...");
// // 2. 初始化检测协调器和AI模型
// // 注意:YoloModelManager 的 Initialize 现在也应在这里被调用,以确保逻辑集中
// YoloModelManager.Initialize(config.ModelSettings);
// DetectionCoordinator.Initialize(config.CameraSettings, config.ModelSettings);
// // 3. 创建相机硬件实例和UI窗口------
// var deviceList = new HikvisionCamera().FindDevices();
// if (deviceList.Count == 0)
// {
// ThreadSafeLogger.Log("错误:未找到任何相机设备!");
// return;
// }
// foreach (var setting in config.CameraSettings.Where(s => s.IsEnabled))//打开相机 → 设置触发模式 → 绑定事件(图像采集、检测完成)。
// {
// var cam = new HikvisionCamera { Name = setting.Name, CameraIndex = setting.CameraIndex };
// cam.TriggerMode = setting.TriggerMode;
// if (!cam.Open(setting))
// {
// ThreadSafeLogger.Log($"错误:打开相机'{setting.Name}'失败。");
// cam.Dispose();
// continue;
// }
// // --- 设置触发模式 ---
// switch (setting.TriggerMode)
// {
// case TriggerModeType.Continuous:
// cam.SetContinuousMode();
// break;
// case TriggerModeType.Software:
// cam.SetTriggerMode(true);
// break;
// case TriggerModeType.Hardware:
// cam.SetTriggerMode(false);
// break;
// }
// // --- 订阅事件 ---
// cam.ImageAcquired += OnCameraImageAcquired;
// var processor = DetectionCoordinator.GetProcessor(cam.CameraIndex);
// if (processor != null) { processor.OnProcessingCompleted += Processor_OnProcessingCompleted; }
// // --- 创建【但不显示】图像的UI窗口 ---
// var originalDisplay = new FormImageDisplay { Text = $"{setting.Name} - 原图", CameraName = setting.Name };
// var resultDisplay = new FormImageDisplay { Text = $"{setting.Name} - 结果", CameraName = setting.Name };
// originalDisplay.Show(mainForm.MainDockPanel, WeifenLuo.WinFormsUI.Docking.DockState.Document);
// resultDisplay.Show(mainForm.MainDockPanel, WeifenLuo.WinFormsUI.Docking.DockState.Document);
// // --- 保存引用 ---
// ActiveCameras.Add(setting.Name, cam);
// OriginalImageDisplays.Add(setting.Name, originalDisplay);
// ResultImageDisplays.Add(setting.Name, resultDisplay);
// mainForm.AddCameraToStatusStrip(setting.Name);
// }
// ThreadSafeLogger.Log("所有设备和模型已准备就绪。");
//}
///
/// 准备所有相机硬件、UI窗口和后台处理器,但不开始采集。
/// 这是“启动设备”的第一阶段。
///
public static void PrepareAll(ProcessConfig config, FrmMain mainForm)
{
mainForm.ClearStatusStrip(); // 清理旧的状态条
Shutdown(); // 清理旧的相机和协调器资源
ThreadSafeLogger.Log("开始准备设备和模型...");
// 1. 初始化检测协调器和AI模型(此步骤会加载YOLO模型并创建CameraProcessor)
DetectionCoordinator.Initialize(config.CameraSettings, config.ModelSettings);
// 2. 创建相机硬件实例和UI窗口
var deviceList = new HikvisionCamera().FindDevices();
if (deviceList.Count == 0)
{
ThreadSafeLogger.Log("错误:未找到任何相机设备!");
return;
}
int deviceIndex = 0;
foreach (var setting in config.CameraSettings.Where(s => s.IsEnabled))
{
if (deviceIndex >= deviceList.Count)
{
ThreadSafeLogger.Log($"警告:相机配置'{setting.Name}'无法分配物理设备,因为设备数量不足。");
continue;
}
var cam = new HikvisionCamera { Name = setting.Name, CameraIndex = setting.CameraIndex };
cam.TriggerMode = setting.TriggerMode;
if (!cam.Open(setting))
{
ThreadSafeLogger.Log($"错误:打开相机'{setting.Name}'失败。");
cam.Dispose();
continue;
}
// --- 设置触发模式 ---
switch (setting.TriggerMode)
{
case TriggerModeType.Continuous:
cam.SetContinuousMode();
break;
case TriggerModeType.Software:
cam.SetTriggerMode(true);
break;
case TriggerModeType.Hardware:
cam.SetTriggerMode(false);
break;
}
// --- 订阅事件 ---
cam.ImageAcquired += OnCameraImageAcquired;
cam.CameraMessage += (sender, msg, err) => ThreadSafeLogger.Log($"{(sender as HikvisionCamera).Name}: {msg}");
// 获取 CameraProcessor 并订阅其完成事件
var processor = DetectionCoordinator.GetProcessor(cam.CameraIndex);
if (processor != null)
{
processor.OnProcessingCompleted += Processor_OnProcessingCompleted;
}
else
{
ThreadSafeLogger.Log($"[警告] 未能为相机 '{setting.Name}' (Index: {setting.CameraIndex}) 获取处理器,检测结果将不会显示。");
}
// --- 创建【并显示】图像的UI窗口 ---
var originalDisplay = new FormImageDisplay { Text = $"{setting.Name} - 原图", CameraName = setting.Name };
var resultDisplay = new FormImageDisplay { Text = $"{setting.Name} - 结果", CameraName = setting.Name };
originalDisplay.OnDisplayEvent += ThreadSafeLogger.Log;
resultDisplay.OnDisplayEvent += ThreadSafeLogger.Log;
originalDisplay.Show(mainForm.MainDockPanel, WeifenLuo.WinFormsUI.Docking.DockState.Document);
resultDisplay.Show(mainForm.MainDockPanel, WeifenLuo.WinFormsUI.Docking.DockState.Document);
// --- 保存引用 ---
ActiveCameras.Add(setting.Name, cam);
OriginalImageDisplays.Add(setting.Name, originalDisplay);
ResultImageDisplays.Add(setting.Name, resultDisplay);
mainForm.AddCameraToStatusStrip(setting.Name);
ThreadSafeLogger.Log($"相机'{setting.Name}'初始化成功,分配到物理设备 {deviceIndex}。");
deviceIndex++;
}
ThreadSafeLogger.Log("所有设备和模型已准备就绪。");
}
///
/// 根据配置列表初始化或更新所有相机
/// 类似 PrepareAll,但更侧重于更新配置,会检查物理设备数量,不足时报 warning。
///
public static void Initialize(ProcessConfig config, FrmMain mainForm)
{
mainForm?.ClearStatusStrip();
// 先停止并释放所有旧的相机
Shutdown();
ThreadSafeLogger.Log("开始应用新的相机配置...");
// 2. 初始化新的检测协调器
DetectionCoordinator.Initialize(config.CameraSettings, config.ModelSettings);
var deviceList = new HikvisionCamera().FindDevices();
if (deviceList.Count == 0)
{
ThreadSafeLogger.Log("错误:未找到任何相机设备!");
return;
}
int deviceIndex = 0;
foreach (var device in config.ModelSettings.Where(s => s.IsEnabled))
{
if (!device.IsEnabled) continue;
mainForm.AddCameraToStatusStrip(device.Name);
}
foreach (var setting in config.CameraSettings.Where(s => s.IsEnabled))
{
if (!setting.IsEnabled) continue;
if (deviceIndex >= deviceList.Count)
{
ThreadSafeLogger.Log($"警告:相机配置'{setting.Name}'无法分配物理设备,因为设备数量不足。");
continue;
}
// --- 创建相机实例 ---
var cam = new HikvisionCamera { Name = setting.Name, CameraIndex = setting.CameraIndex };
cam.TriggerMode = setting.TriggerMode;
if (!cam.Open(setting))
{
ThreadSafeLogger.Log($"错误:打开相机'{setting.Name}'失败。");
cam.Dispose();
continue;
}
// --- 设置触发模式 ---
switch (setting.TriggerMode)
{
case TriggerModeType.Continuous:
cam.SetContinuousMode();
break;
case TriggerModeType.Software:
cam.SetTriggerMode(true);
break;
case TriggerModeType.Hardware:
cam.SetTriggerMode(false);
break;
}
// --- 订阅事件 ---
cam.ImageAcquired += OnCameraImageAcquired;
cam.CameraMessage += (sender, msg, err) => ThreadSafeLogger.Log($"{(sender as HikvisionCamera).Name}: {msg}");
ActiveCameras.Add(setting.Name, cam);
// --- 创建显示窗口 ---
var displayForm = new FormImageDisplay { Text = $"{setting.Name} - 原图", CameraName = setting.Name };
var checkFrm = new FormImageDisplay { Text = $"{setting.Name} - 结果", CameraName = setting.Name };
displayForm.OnDisplayEvent += ThreadSafeLogger.Log;
displayForm.Show(mainForm.MainDockPanel, WeifenLuo.WinFormsUI.Docking.DockState.Document);
checkFrm.Show(mainForm.MainDockPanel, WeifenLuo.WinFormsUI.Docking.DockState.Document);
OriginalImageDisplays.Add(setting.Name, displayForm);
ResultImageDisplays.Add(setting.Name, checkFrm);
mainForm.AddCameraToStatusStrip(setting.Name);
var processor = DetectionCoordinator.GetProcessor(cam.CameraIndex);
if (processor != null)
{
processor.OnProcessingCompleted += Processor_OnProcessingCompleted;
}
ThreadSafeLogger.Log($"相机'{setting.Name}'初始化成功,分配到物理设备 {deviceIndex}。");
deviceIndex++;
}
}
///
/// 启动硬件触发模拟器。
///
public static void StartHardwareTriggerSimulator()
{
if (IsHardwareTriggerSimulating) return;
_hardwareTriggerSimulator.Interval = TriggerInterval;
_hardwareTriggerSimulator.Start();
IsHardwareTriggerSimulating = true;
ThreadSafeLogger.Log($"硬件触发模拟器已启动,触发间隔: {TriggerInterval} ms。");
}
///
/// 停止硬件触发模拟器。
///
public static void StopHardwareTriggerSimulator()
{
if (!IsHardwareTriggerSimulating) return;
_hardwareTriggerSimulator.Stop();
IsHardwareTriggerSimulating = false;
ThreadSafeLogger.Log("硬件触发模拟器已停止。");
}
///
/// 定时器触发事件。(定时器 OnHardwareTriggerTimerElapsed 会遍历相机,对处于软件触发模式的相机执行 SoftwareTrigger()。)
///
private static void OnHardwareTriggerTimerElapsed(object sender, ElapsedEventArgs e)
{
// 遍历所有活动的相机
foreach (var cam in ActiveCameras.Values)
{
// 仅对配置为“硬件触发”模式的相机执行操作
// 重要:我们使用软触发命令(SoftwareTrigger)来“模拟”一个外部硬件信号的到达。
if (cam.TriggerMode == TriggerModeType.Software && cam.IsGrabbing)
{
// ThreadSafeLogger.Log($"模拟硬触发信号,触发相机: {cam.Name}"); // 如果需要详细日志可以取消注释
cam.SoftwareTrigger();
}
}
}
///
/// 所有启用的相机开始采集
///
public static void StartAll()
{
foreach (var cam in ActiveCameras.Values)
{
cam.StartGrabbing();
}
ThreadSafeLogger.Log("所有相机已开始采集。");
}
//public static void StartDetection()
//{
// if (!IsDetectionRunning)
// {
// IsDetectionRunning = true;
// ThreadSafeLogger.Log("检测已启动,开始统计数据。");
// }
//}
//public static void StopDetection()
//{
// if (IsDetectionRunning)
// {
// IsDetectionRunning = false;
// ThreadSafeLogger.Log("检测已停止。");
// }
//}
///
/// 所有启用的相机停止采集
///
public static void StopAll()
{
foreach (var cam in ActiveCameras.Values)
{
cam.StopGrabbing();
}
ThreadSafeLogger.Log("所有相机已停止采集。");
}
/////
///// 停止并释放所有相机资源
/////
//public static void Shutdown()
//{
// // --- 新增:确保在关闭时停止模拟器并释放资源 ---
// StopHardwareTriggerSimulator();
// //_hardwareTriggerSimulator?.Dispose();
// StopAll();
// foreach (var cam in ActiveCameras.Values)
// {
// cam.Dispose();
// }
// foreach (var display in CameraDisplays.Values)
// {
// display.Close();
// }
// lock (QueueLock)
// {
// while (ProductQueue.Count > 0)
// {
// ProductQueue.Dequeue()?.Dispose();
// }
// }
// ResetProductCounter();
// ActiveCameras.Clear();
// CameraDisplays.Clear();
// ThreadSafeLogger.Log("正在关闭文件日志记录器...");
// ThreadSafeLogger.Log("所有相机资源已释放。");
//}
///
/// 停止所有相机 + 关闭窗口 + 清空字典 + 关闭检测协调器 + 销毁模型
///
public static void Shutdown()
{
// 1. 停止硬件和模拟器
StopAll();
StopHardwareTriggerSimulator();
// 2. 关闭相机实例和窗口
foreach (var cam in ActiveCameras.Values) { cam.Dispose(); }
foreach (var display in OriginalImageDisplays.Values) { display.Close(); }
foreach (var display in ResultImageDisplays.Values) { display.Close(); }
ActiveCameras.Clear();
OriginalImageDisplays.Clear();
ResultImageDisplays.Clear();
// 3. 关闭检测协调器,它会负责清理所有后台线程和队列
DetectionCoordinator.Shutdown();
ThreadSafeLogger.Log("所有相机及协调器资源已释放。");
}
///
/// 重置产品计数器的公共方法
///
public static void ResetProductCounter()
{
//lock (_counterLock)
//{
// _productCounter = 0;
//}
DetectionCoordinator.ResetAllCounters(); // 调用协调器的重置方法
ThreadSafeLogger.Log("产品计数器已重置。");
}
///
/// 接收到相机图像时的核心处理逻辑
///
//private static void OnCameraImageAcquired(HikvisionCamera sender, Bitmap bmp)
//{
// Bitmap bmpForDisplay =null;
// Bitmap bmpForQueue = null;
// try
// {
// bmpForDisplay?.Dispose();
// bmpForQueue?.Dispose();
// // 1. 为“显示”和“处理队列”创建独立的深克隆副本
// bmpForDisplay = DeepCloneBitmap(bmp, "Display");
// bmpForQueue = DeepCloneBitmap(bmp, "Queue");
// }
// finally
// {
// // 【关键】无论克隆成功与否,都必须立即释放事件传递过来的原始bmp。
// // 这是保证没有泄漏的第一道防线。
// bmp?.Dispose();
// }
// // --- 现在我们使用完全独立的副本进行后续操作 ---
// // 4. 将显示副本传递给UI
// if (bmpForDisplay != null && CameraDisplays.TryGetValue(sender.Name, out var display))
// {
// display.UpdateImage(bmpForDisplay);
// }
// else
// {
// // 如果不需要显示,或者显示失败,必须释放掉为它创建的副本
// bmpForDisplay?.Dispose();
// }
// // 5. 将队列副本添加到产品中
// if (bmpForQueue != null)
// {
// lock (QueueLock)
// {
// ProductResult currentProduct;
// bool isNewProductCycle = ProductQueue.Count == 0 || ProductQueue.Last().CapturedImages.ContainsKey(sender.Name);
// if (isNewProductCycle)
// {
// if (ProductQueue.Count > 0 && !ProductQueue.Peek().IsComplete(EnabledCameraCount))
// {
// var orphanedProduct = ProductQueue.Dequeue(); // 从队列头部移除这个不完整的产品
// ThreadSafeLogger.Log($"[警告] 产品 #{orphanedProduct.ProductID} 未能集齐所有相机图像而被丢弃,以防止内存泄漏。");
// orphanedProduct.Dispose(); // 确保释放其占用的所有资源
// }
// long newProductId;
// lock (_counterLock)
// {
// _productCounter++;
// newProductId = _productCounter;
// }
// currentProduct = new ProductResult(newProductId);
// ProductQueue.Enqueue(currentProduct);
// }
// else
// {
// currentProduct = ProductQueue.Last();
// }
// currentProduct.AddImage(sender.Name, bmpForQueue); // bmpForQueue的所有权转移给了产品队列
// while (ProductQueue.Count > 0 && ProductQueue.Peek().IsComplete(EnabledCameraCount))
// {
// // Peek() 查看队头元素但不移除它
// var finishedProduct = ProductQueue.Dequeue(); // Dequeue() 移除队头元素
// ThreadSafeLogger.Log($"产品 #{finishedProduct.ProductID} 已完整,出队处理。");
// // --- 这是您已有的处理逻辑 ---
// bool isOk = new Random().NextDouble() > 0.1;
// string finalResult = isOk ? "OK" : "NG";
// ThreadSafeLogger.Log($"产品 #{finishedProduct.ProductID} 检测结果: {finalResult}");
// if (IsDetectionRunning)
// {
// OnDetectionCompleted?.Invoke(null, new DetectionResultEventArgs(isOk));
// }
// try
// {
// // 确保完成的产品被完全释放
// finishedProduct.Dispose();
// }
// catch (Exception ex)
// {
// ThreadSafeLogger.Log($"[ERROR] 释放产品 #{finishedProduct.ProductID} 资源时出错: {ex.Message}");
// }
// }
// }
// }
//}
//【相机回调】
// 【相机回调】现在只负责图像分发
private static void OnCameraImageAcquired(HikvisionCamera sender, Bitmap bmp)
{
Bitmap bmpForDisplay = null;
Bitmap bmpForProcessing = null;
try
{
bmpForDisplay = DeepCloneBitmap(bmp, "Display");
bmpForProcessing = DeepCloneBitmap(bmp, "Processing");
}
finally
{
bmp?.Dispose();
}
if (bmpForDisplay != null && OriginalImageDisplays.TryGetValue(sender.Name, out var displayWindow))
{
displayWindow.UpdateImage(bmpForDisplay);
}
else
{
bmpForDisplay?.Dispose();
}
// 直接将处理副本和相机编号交给 DetectionCoordinator
if (bmpForProcessing != null)
{
DetectionCoordinator.EnqueueImage(sender.CameraIndex, bmpForProcessing);
}
}
// 事件处理器:从 CameraProcessor 接收带有结果的图像
private static void Processor_OnProcessingCompleted(object sender, ProcessingCompletedEventArgs e)
{
var cameraEntry = ActiveCameras.FirstOrDefault(kvp => kvp.Value.CameraIndex == e.CameraIndex);
if (cameraEntry.Key == null)
{
e.Dispose();
return;
}
if (ResultImageDisplays.TryGetValue(cameraEntry.Key, out var resultDisplay))
{
if (e.ResultImage != null)
{
resultDisplay.UpdateImage(e.ResultImage);
}
}
else
{
e.Dispose();
}
}
///
/// 【将 SkiaSharp.SKImage 安全地转换为 System.Drawing.Bitmap。
///
private static Bitmap ConvertSKImageToBitmap(SKImage skImage)
{
if (skImage == null) return null;
try
{
// SKImage -> SKBitmap -> System.Drawing.Bitmap
using (var skBitmap = SKBitmap.FromImage(skImage))
{
// SKBitmap.ToBitmap() 会创建一个新的 Bitmap 对象
return SKBitmapToGdiBitmapFast(skBitmap);
}
}
catch (Exception ex)
{
ThreadSafeLogger.Log($"[错误] SKImage to Bitmap 转换失败: {ex.Message}");
return null;
}
}
public static Bitmap SKBitmapToGdiBitmapFast(SKBitmap skBitmap)
{
if (skBitmap == null) throw new ArgumentNullException(nameof(skBitmap));
if (skBitmap.ColorType != SKColorType.Bgra8888 || skBitmap.AlphaType != SKAlphaType.Premul)
throw new ArgumentException("skBitmap must be Bgra8888 + Premul for the fast path.");
int w = skBitmap.Width;
int h = skBitmap.Height;
Bitmap bmp = new Bitmap(w, h, PixelFormat.Format32bppArgb);
var rect = new Rectangle(0, 0, w, h);
var bmpData = bmp.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
try
{
IntPtr srcPtr = skBitmap.GetPixels();
int srcRowBytes = skBitmap.RowBytes;
int dstRowBytes = bmpData.Stride;
int copyBytesPerRow = Math.Min(srcRowBytes, dstRowBytes);
byte[] row = new byte[copyBytesPerRow]; // 复用同一行缓冲区,避免每行分配
for (int y = 0; y < h; y++)
{
IntPtr s = IntPtr.Add(srcPtr, y * srcRowBytes);
IntPtr d = IntPtr.Add(bmpData.Scan0, y * dstRowBytes);
Marshal.Copy(s, row, 0, copyBytesPerRow);
Marshal.Copy(row, 0, d, copyBytesPerRow);
}
}
finally
{
bmp.UnlockBits(bmpData);
}
return bmp;
}
///
/// 对Bitmap进行真正的深度克隆,确保内存完全独立。
/// 这是解决跨线程GDI+问题的最可靠方法。
///
/// 源Bitmap对象。
/// 一个与源图像在内存上完全独立的全新Bitmap对象。
private static Bitmap DeepCloneBitmap(Bitmap source, string cloneFor)
{
if (source == null) return null;
// 创建一个新的Bitmap对象,具有与源相同的尺寸和像素格式
Bitmap clone = new Bitmap(source.Width, source.Height, source.PixelFormat);
// 如果有调色板,复制调色板
if (source.Palette.Entries.Length > 0)
{
clone.Palette = source.Palette;
}
// 锁定源和目标Bitmap的内存区域
var rect = new Rectangle(0, 0, source.Width, source.Height);
BitmapData sourceData = null; //source.LockBits(rect, ImageLockMode.ReadOnly, source.PixelFormat);
BitmapData cloneData = null;//clone.LockBits(rect, ImageLockMode.WriteOnly, clone.PixelFormat);
try
{
sourceData = source.LockBits(rect, ImageLockMode.ReadOnly, source.PixelFormat);
cloneData = clone.LockBits(rect, ImageLockMode.WriteOnly, clone.PixelFormat);
// 计算需要拷贝的字节数
int byteCount = Math.Abs(sourceData.Stride) * source.Height;
byte[] buffer = new byte[byteCount];
// 从源图像拷贝数据到字节数组
Marshal.Copy(sourceData.Scan0, buffer, 0, byteCount);
// 从字节数组拷贝数据到目标图像
Marshal.Copy(buffer, 0, cloneData.Scan0, byteCount);
}
catch (Exception ex)
{
ThreadSafeLogger.Log($"[克隆错误] 在 DeepCloneBitmap ({cloneFor}) 中发生异常: {ex.Message}");
// 如果发生错误,确保返回null,并且释放可能已经创建的clone对象
clone?.Dispose();
return null;
}
finally
{
// 确保即使在发生错误时也能尝试解锁
if (sourceData != null)
{
source.UnlockBits(sourceData);
}
if (cloneData != null)
{
clone.UnlockBits(cloneData);
}
//ThreadSafeLogger.Log($"[克隆完成] 解锁并完成克隆 ({cloneFor}).");
}
return clone;
}
}
}