From 98d4a4213c2ffbb107534d70c535559f8f42bada Mon Sep 17 00:00:00 2001 From: CJKmkp <2564608840@qq.com> Date: Sat, 13 Sep 2025 13:59:54 +0800 Subject: [PATCH] =?UTF-8?q?improve:=E6=88=AA=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/Helpers/CameraService.cs | 261 ++++++++++++++ Ink Canvas/InkCanvasForClass.csproj | 2 + Ink Canvas/MainWindow_cs/MW_ImageInsert.cs | 75 ++-- .../Windows/ScreenshotSelectorWindow.xaml | 71 ++++ .../Windows/ScreenshotSelectorWindow.xaml.cs | 328 +++++++++++++++++- ...vasForClass.csproj.AssemblyReference.cache | Bin 35826 -> 38181 bytes 6 files changed, 700 insertions(+), 37 deletions(-) create mode 100644 Ink Canvas/Helpers/CameraService.cs diff --git a/Ink Canvas/Helpers/CameraService.cs b/Ink Canvas/Helpers/CameraService.cs new file mode 100644 index 00000000..9e18fb96 --- /dev/null +++ b/Ink Canvas/Helpers/CameraService.cs @@ -0,0 +1,261 @@ +using AForge.Video; +using AForge.Video.DirectShow; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Windows.Media.Imaging; +using System.Windows.Threading; + +namespace Ink_Canvas.Helpers +{ + public class CameraService : IDisposable + { + private VideoCaptureDevice _videoSource; + private bool _isCapturing; + private Bitmap _currentFrame; + private readonly object _frameLock = new object(); + private Dispatcher _dispatcher; + + public event EventHandler FrameReceived; + public event EventHandler ErrorOccurred; + + public bool IsCapturing => _isCapturing; + public List AvailableCameras { get; private set; } + public FilterInfo CurrentCamera { get; private set; } + + public CameraService() + { + _dispatcher = Dispatcher.CurrentDispatcher; + AvailableCameras = new List(); + RefreshCameraList(); + } + + /// + /// 刷新可用摄像头列表 + /// + public void RefreshCameraList() + { + try + { + AvailableCameras.Clear(); + var videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice); + + foreach (FilterInfo device in videoDevices) + { + AvailableCameras.Add(device); + } + + LogHelper.WriteLogToFile($"发现 {AvailableCameras.Count} 个摄像头设备"); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"刷新摄像头列表失败: {ex.Message}", LogHelper.LogType.Error); + ErrorOccurred?.Invoke(this, $"刷新摄像头列表失败: {ex.Message}"); + } + } + + /// + /// 开始摄像头预览 + /// + /// 摄像头索引 + public bool StartPreview(int cameraIndex = 0) + { + try + { + if (AvailableCameras.Count == 0) + { + RefreshCameraList(); + if (AvailableCameras.Count == 0) + { + ErrorOccurred?.Invoke(this, "未找到可用的摄像头设备"); + return false; + } + } + + if (cameraIndex < 0 || cameraIndex >= AvailableCameras.Count) + { + ErrorOccurred?.Invoke(this, "摄像头索引超出范围"); + return false; + } + + // 停止当前预览 + StopPreview(); + + CurrentCamera = AvailableCameras[cameraIndex]; + _videoSource = new VideoCaptureDevice(CurrentCamera.MonikerString); + + // 设置视频源事件处理 + _videoSource.NewFrame += VideoSource_NewFrame; + + // 启动视频源 + _videoSource.Start(); + + _isCapturing = true; + LogHelper.WriteLogToFile($"开始摄像头预览: {CurrentCamera.Name}"); + return true; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"启动摄像头预览失败: {ex.Message}", LogHelper.LogType.Error); + ErrorOccurred?.Invoke(this, $"启动摄像头预览失败: {ex.Message}"); + return false; + } + } + + /// + /// 停止摄像头预览 + /// + public void StopPreview() + { + try + { + if (_videoSource != null && _videoSource.IsRunning) + { + _videoSource.SignalToStop(); + _videoSource.WaitForStop(); + _videoSource.NewFrame -= VideoSource_NewFrame; + _videoSource = null; + } + + _isCapturing = false; + LogHelper.WriteLogToFile("摄像头预览已停止"); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"停止摄像头预览失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 切换到指定摄像头 + /// + /// 摄像头索引 + public bool SwitchCamera(int cameraIndex) + { + try + { + if (cameraIndex < 0 || cameraIndex >= AvailableCameras.Count) + { + ErrorOccurred?.Invoke(this, "摄像头索引超出范围"); + return false; + } + + return StartPreview(cameraIndex); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"切换摄像头失败: {ex.Message}", LogHelper.LogType.Error); + ErrorOccurred?.Invoke(this, $"切换摄像头失败: {ex.Message}"); + return false; + } + } + + /// + /// 获取当前帧的Bitmap + /// + public Bitmap GetCurrentFrame() + { + lock (_frameLock) + { + return _currentFrame?.Clone() as Bitmap; + } + } + + /// + /// 获取当前帧的BitmapSource(WPF格式) + /// + public BitmapSource GetCurrentFrameAsBitmapSource() + { + lock (_frameLock) + { + if (_currentFrame == null) + return null; + + try + { + using (var memory = new MemoryStream()) + { + _currentFrame.Save(memory, ImageFormat.Png); + memory.Position = 0; + + var bitmapImage = new BitmapImage(); + bitmapImage.BeginInit(); + bitmapImage.CacheOption = BitmapCacheOption.OnLoad; + bitmapImage.StreamSource = memory; + bitmapImage.EndInit(); + bitmapImage.Freeze(); + + return bitmapImage; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"转换帧为BitmapSource失败: {ex.Message}", LogHelper.LogType.Error); + return null; + } + } + } + + /// + /// 视频源新帧事件处理 + /// + private void VideoSource_NewFrame(object sender, NewFrameEventArgs eventArgs) + { + try + { + lock (_frameLock) + { + // 释放之前的帧 + _currentFrame?.Dispose(); + + // 克隆新帧 + _currentFrame = eventArgs.Frame.Clone() as Bitmap; + } + + // 在UI线程中触发事件 + _dispatcher.BeginInvoke(new Action(() => + { + FrameReceived?.Invoke(this, _currentFrame); + })); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理新帧失败: {ex.Message}", LogHelper.LogType.Error); + ErrorOccurred?.Invoke(this, $"处理新帧失败: {ex.Message}"); + } + } + + /// + /// 获取摄像头名称列表 + /// + public List GetCameraNames() + { + return AvailableCameras.Select(camera => camera.Name).ToList(); + } + + /// + /// 检查是否有可用摄像头 + /// + public bool HasAvailableCameras() + { + if (AvailableCameras.Count == 0) + { + RefreshCameraList(); + } + return AvailableCameras.Count > 0; + } + + public void Dispose() + { + StopPreview(); + + lock (_frameLock) + { + _currentFrame?.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Ink Canvas/InkCanvasForClass.csproj b/Ink Canvas/InkCanvasForClass.csproj index 9bbef5ac..8d67e1d4 100644 --- a/Ink Canvas/InkCanvasForClass.csproj +++ b/Ink Canvas/InkCanvasForClass.csproj @@ -158,6 +158,8 @@ + + diff --git a/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs b/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs index 82193558..2848ae09 100644 --- a/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs +++ b/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs @@ -27,11 +27,13 @@ namespace Ink_Canvas { public Rectangle Area; public List Path; + public Bitmap CameraImage; - public ScreenshotResult(Rectangle area, List path = null) + public ScreenshotResult(Rectangle area, List path = null, Bitmap cameraImage = null) { Area = area; Path = path; + CameraImage = cameraImage; } } @@ -55,34 +57,43 @@ namespace Ink_Canvas // 恢复窗口显示 Visibility = originalVisibility; - if (screenshotResult.HasValue && screenshotResult.Value.Area.Width > 0 && screenshotResult.Value.Area.Height > 0) + if (screenshotResult.HasValue) { - // 截取选定区域 - using (var originalBitmap = CaptureScreenArea(screenshotResult.Value.Area)) + // 检查是否是摄像头截图 + if (screenshotResult.Value.CameraImage != null) { - if (originalBitmap != null) + // 摄像头截图 + await InsertScreenshotToCanvas(screenshotResult.Value.CameraImage); + } + else if (screenshotResult.Value.Area.Width > 0 && screenshotResult.Value.Area.Height > 0) + { + // 屏幕截图 + using (var originalBitmap = CaptureScreenArea(screenshotResult.Value.Area)) { - Bitmap finalBitmap = originalBitmap; - bool needDisposeFinalBitmap = false; - - try + if (originalBitmap != null) { - // 如果有路径信息,应用形状遮罩 - if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0) + Bitmap finalBitmap = originalBitmap; + bool needDisposeFinalBitmap = false; + + try { - finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area); - needDisposeFinalBitmap = true; // 标记需要释放新创建的位图 + // 如果有路径信息,应用形状遮罩 + if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0) + { + finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area); + needDisposeFinalBitmap = true; // 标记需要释放新创建的位图 + } + + // 将截图转换为WPF Image并插入到画布 + await InsertScreenshotToCanvas(finalBitmap); } - - // 将截图转换为WPF Image并插入到画布 - await InsertScreenshotToCanvas(finalBitmap); - } - finally - { - // 如果创建了新的位图,需要释放它 - if (needDisposeFinalBitmap && finalBitmap != originalBitmap) + finally { - finalBitmap.Dispose(); + // 如果创建了新的位图,需要释放它 + if (needDisposeFinalBitmap && finalBitmap != originalBitmap) + { + finalBitmap.Dispose(); + } } } } @@ -152,10 +163,22 @@ namespace Ink_Canvas var selectorWindow = new ScreenshotSelectorWindow(); if (selectorWindow.ShowDialog() == true) { - result = new ScreenshotResult( - selectorWindow.SelectedArea.Value, - selectorWindow.SelectedPath - ); + // 检查是否是摄像头截图 + if (selectorWindow.CameraImage != null) + { + result = new ScreenshotResult( + Rectangle.Empty, // 摄像头截图不需要区域 + null, // 摄像头截图不需要路径 + selectorWindow.CameraImage // 摄像头图像 + ); + } + else + { + result = new ScreenshotResult( + selectorWindow.SelectedArea.Value, + selectorWindow.SelectedPath + ); + } } }); } diff --git a/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml b/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml index 5980b511..b4832ff9 100644 --- a/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml +++ b/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml @@ -124,6 +124,15 @@ BorderThickness="0" FontWeight="Medium" Click="FullScreenButton_Click" /> +