From 807288ab906fb0f7e54a96ccd407501f8a4559e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sat, 15 Mar 2025 13:18:19 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E5=BA=95=E5=B1=82=E6=88=AA?= =?UTF-8?q?=E5=9B=BE=E5=99=A8=EF=BC=8C=E5=A4=A7=E5=B9=85=E6=8F=90=E5=8D=87?= =?UTF-8?q?=E6=88=AA=E5=9B=BE=E5=99=A8=E8=80=97=E6=97=B6=20(#1302)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * to mat init * BitBlt 加锁 * 使用读写锁重构 Windows.Graphics.Capture,删除BGI自己命名的缓存设置 * dwm加锁并返回mat * 队伍中没有对应元素角色修复日志问题 * 清除所有 DispatcherTimerOperationEnum 内容 * 修复单测的编译错误 * HDR Support * 清理无用的截图器模式 --- .../BetterGenshinImpact.csproj | 1 + BetterGenshinImpact/Core/Config/AllConfig.cs | 14 +- .../Core/Recorder/DirectInputCalibration.cs | 2 +- .../Core/Script/Dependence/Dispatcher.cs | 2 - .../GameTask/AutoFight/OneKeyFightTask.cs | 10 +- .../GameTask/AutoFishing/AutoFishingTask.cs | 2 +- .../GameTask/AutoPathing/PathExecutor.cs | 2 +- .../AutoTrackPath/AutoTrackPathTask.cs | 5 +- .../AutoTrackPath/PathPointRecorder.cs | 6 +- .../GameTask/CaptureContent.cs | 10 +- .../GameTask/Common/TaskControl.cs | 76 ++---- .../GameTask/Model/Area/DesktopRegion.cs | 5 +- .../GameTask/Model/Area/GameCaptureRegion.cs | 2 +- .../GameTask/Model/Area/ImageRegion.cs | 2 +- .../GameTask/Model/BaseTaskParam.cs | 2 +- .../Model/Enum/DispatcherCaptureModeEnum.cs | 29 --- .../Enum/DispatcherTimerOperationEnum.cs | 24 -- BetterGenshinImpact/GameTask/TaskRunner.cs | 56 +---- .../GameTask/TaskTriggerDispatcher.cs | 201 ++++------------ .../Notification/NotificationService.cs | 11 +- BetterGenshinImpact/Service/ScriptService.cs | 90 +++---- .../View/CaptureTestWindow.xaml.cs | 4 +- BetterGenshinImpact/View/Pages/HomePage.xaml | 4 +- .../Pages/CommonSettingsPageViewModel.cs | 8 - .../Pages/KeyMouseRecordPageViewModel.cs | 3 +- .../ViewModel/Pages/OneDragonFlowViewModel.cs | 4 +- .../Pages/TaskSettingsPageViewModel.cs | 16 +- Fischless.GameCapture/BitBlt/BitBltCapture.cs | 92 +++++-- .../DwmSharedSurface/SharedSurfaceCapture.cs | 51 ++-- .../Fischless.GameCapture.csproj | 2 + .../Graphics/GraphicsCapture.cs | 224 +++++++++++++++--- .../Graphics/Helpers/Texture2DExtensions.cs | 37 +++ Fischless.GameCapture/IGameCapture.cs | 6 +- .../BehavioursTests.FishBite.cs | 7 +- .../BehavioursTests.GetFishBoxArea.cs | 5 +- .../BehavioursTests.GetFishpond.cs | 7 +- .../BehavioursTests.ThrowRod.cs | 13 +- 37 files changed, 505 insertions(+), 530 deletions(-) delete mode 100644 BetterGenshinImpact/GameTask/Model/Enum/DispatcherCaptureModeEnum.cs delete mode 100644 BetterGenshinImpact/GameTask/Model/Enum/DispatcherTimerOperationEnum.cs diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index efae8eab..375c884b 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -153,6 +153,7 @@ + diff --git a/BetterGenshinImpact/Core/Config/AllConfig.cs b/BetterGenshinImpact/Core/Config/AllConfig.cs index 2fdfb32d..06b1639a 100644 --- a/BetterGenshinImpact/Core/Config/AllConfig.cs +++ b/BetterGenshinImpact/Core/Config/AllConfig.cs @@ -51,13 +51,13 @@ public partial class AllConfig : ObservableObject [ObservableProperty] private int _triggerInterval = 50; - /// - /// WGC使用位图缓存 - /// 高帧率情况下,可能会导致卡顿 - /// 云原神可能会出现黑屏 - /// - [ObservableProperty] - private bool _wgcUseBitmapCache = true; + // /// + // /// WGC使用位图缓存 + // /// 高帧率情况下,可能会导致卡顿 + // /// 云原神可能会出现黑屏 + // /// + // [ObservableProperty] + // private bool _wgcUseBitmapCache = true; /// /// 自动修复Win11下BitBlt截图方式不可用的问题 diff --git a/BetterGenshinImpact/Core/Recorder/DirectInputCalibration.cs b/BetterGenshinImpact/Core/Recorder/DirectInputCalibration.cs index 91599fd9..ad3977b3 100644 --- a/BetterGenshinImpact/Core/Recorder/DirectInputCalibration.cs +++ b/BetterGenshinImpact/Core/Recorder/DirectInputCalibration.cs @@ -8,7 +8,7 @@ // using BetterGenshinImpact.GameTask.Common.Element.Assets; // using BetterGenshinImpact.GameTask.Common.Map; // using BetterGenshinImpact.GameTask.Model.Area; -// using BetterGenshinImpact.GameTask.Model.Enum; +// // using BetterGenshinImpact.View.Drawable; // using BetterGenshinImpact.ViewModel.Pages; // using Microsoft.Extensions.Logging; diff --git a/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs b/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs index 9e87f34f..4d19c4a3 100644 --- a/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs +++ b/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs @@ -4,10 +4,8 @@ using BetterGenshinImpact.ViewModel.Pages; using System; using System.Threading.Tasks; using BetterGenshinImpact.GameTask.AutoDomain; -using BetterGenshinImpact.GameTask.AutoFight; using BetterGenshinImpact.GameTask.AutoFishing; using BetterGenshinImpact.GameTask.AutoWood; -using BetterGenshinImpact.GameTask.Model.Enum; using BetterGenshinImpact.GameTask.AutoGeniusInvokation; using BetterGenshinImpact.GameTask.AutoPathing.Handler; diff --git a/BetterGenshinImpact/GameTask/AutoFight/OneKeyFightTask.cs b/BetterGenshinImpact/GameTask/AutoFight/OneKeyFightTask.cs index a8143010..5f5dfc0a 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/OneKeyFightTask.cs +++ b/BetterGenshinImpact/GameTask/AutoFight/OneKeyFightTask.cs @@ -1,7 +1,7 @@ using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.GameTask.AutoFight.Model; using BetterGenshinImpact.GameTask.AutoFight.Script; -using BetterGenshinImpact.GameTask.Model.Enum; + using BetterGenshinImpact.Model; using BetterGenshinImpact.Service; using Microsoft.Extensions.Logging; @@ -128,14 +128,6 @@ public class OneKeyFightTask : Singleton /// private Task FightTask(CancellationToken ct) { - // 切换截图模式 - var dispatcherCaptureMode = TaskTriggerDispatcher.Instance().GetCacheCaptureMode(); - if (dispatcherCaptureMode != DispatcherCaptureModeEnum.CacheCaptureWithTrigger) - { - TaskTriggerDispatcher.Instance().SetCacheCaptureMode(DispatcherCaptureModeEnum.CacheCaptureWithTrigger); - Sleep(TaskContext.Instance().Config.TriggerInterval * 2, ct); // 等待缓存图像 - } - var imageRegion = CaptureToRectArea(); var combatScenes = new CombatScenes().InitializeTeam(imageRegion); if (!combatScenes.CheckTeamInitialized()) diff --git a/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTask.cs b/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTask.cs index 6adce465..b9866a89 100644 --- a/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTask.cs +++ b/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTask.cs @@ -121,7 +121,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing break; } - using var bitmap = TaskControl.CaptureGameBitmapNoRetry(TaskTriggerDispatcher.Instance().GameCapture); + using var bitmap = TaskControl.CaptureGameImageNoRetry(TaskTriggerDispatcher.Instance().GameCapture); if (bitmap == null) { _logger.LogWarning("截图失败"); diff --git a/BetterGenshinImpact/GameTask/AutoPathing/PathExecutor.cs b/BetterGenshinImpact/GameTask/AutoPathing/PathExecutor.cs index 675e2401..5d656b18 100644 --- a/BetterGenshinImpact/GameTask/AutoPathing/PathExecutor.cs +++ b/BetterGenshinImpact/GameTask/AutoPathing/PathExecutor.cs @@ -463,7 +463,7 @@ public class PathExecutor } } - Logger.LogError("此路径存在 {action} 收集动作,队伍中没有对应元素角色:{},无法执行此路径!", action, string.Join(",", ElementalCollectAvatarConfigs.GetAvatarNameList(el))); + Logger.LogError("此路径存在 {El}元素采集 动作,队伍中没有对应元素角色:{Names},无法执行此路径!", el.ToChinese(), string.Join(",", ElementalCollectAvatarConfigs.GetAvatarNameList(el))); return false; } else diff --git a/BetterGenshinImpact/GameTask/AutoTrackPath/AutoTrackPathTask.cs b/BetterGenshinImpact/GameTask/AutoTrackPath/AutoTrackPathTask.cs index bf29b95a..1687b30a 100644 --- a/BetterGenshinImpact/GameTask/AutoTrackPath/AutoTrackPathTask.cs +++ b/BetterGenshinImpact/GameTask/AutoTrackPath/AutoTrackPathTask.cs @@ -9,7 +9,7 @@ using BetterGenshinImpact.GameTask.Common.BgiVision; using BetterGenshinImpact.GameTask.Common.Element.Assets; using BetterGenshinImpact.GameTask.Common.Map; using BetterGenshinImpact.GameTask.Model.Area; -using BetterGenshinImpact.GameTask.Model.Enum; + using BetterGenshinImpact.Helpers; using BetterGenshinImpact.Helpers.Extensions; using BetterGenshinImpact.Service; @@ -87,7 +87,6 @@ public class AutoTrackPathTask finally { VisionContext.Instance().DrawContent.ClearAll(); - TaskTriggerDispatcher.Instance().SetCacheCaptureMode(DispatcherCaptureModeEnum.NormalTrigger); Logger.LogInformation("→ {Text}", "自动路线结束"); if (hasLock) @@ -101,8 +100,6 @@ public class AutoTrackPathTask { SystemControl.ActivateWindow(); Logger.LogInformation("→ {Text}", "自动路线,启动!"); - TaskTriggerDispatcher.Instance().SetCacheCaptureMode(DispatcherCaptureModeEnum.OnlyCacheCapture); - Sleep(TaskContext.Instance().Config.TriggerInterval * 5, _ct); // 等待缓存图像 } public async Task DoTask() diff --git a/BetterGenshinImpact/GameTask/AutoTrackPath/PathPointRecorder.cs b/BetterGenshinImpact/GameTask/AutoTrackPath/PathPointRecorder.cs index 0377058d..50cab4bd 100644 --- a/BetterGenshinImpact/GameTask/AutoTrackPath/PathPointRecorder.cs +++ b/BetterGenshinImpact/GameTask/AutoTrackPath/PathPointRecorder.cs @@ -4,7 +4,7 @@ using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception; using BetterGenshinImpact.GameTask.AutoTrackPath.Model; using BetterGenshinImpact.GameTask.Common.Element.Assets; using BetterGenshinImpact.GameTask.Common.Map; -using BetterGenshinImpact.GameTask.Model.Enum; + using BetterGenshinImpact.Helpers.Extensions; using BetterGenshinImpact.Model; using BetterGenshinImpact.Service; @@ -34,16 +34,12 @@ public class PathPointRecorder : Singleton { if (_recordTask == null) { - TaskTriggerDispatcher.Instance().SetCacheCaptureMode(DispatcherCaptureModeEnum.OnlyCacheCapture); - _recordTaskCts = new CancellationTokenSource(); _recordTask = RecordTask(_recordTaskCts.Token); _recordTask.Start(); } else { - TaskTriggerDispatcher.Instance().SetCacheCaptureMode(DispatcherCaptureModeEnum.NormalTrigger); - _recordTaskCts?.Cancel(); _recordTask = null; } diff --git a/BetterGenshinImpact/GameTask/CaptureContent.cs b/BetterGenshinImpact/GameTask/CaptureContent.cs index c409ef03..c6da11f5 100644 --- a/BetterGenshinImpact/GameTask/CaptureContent.cs +++ b/BetterGenshinImpact/GameTask/CaptureContent.cs @@ -1,6 +1,7 @@ using BetterGenshinImpact.GameTask.Model.Area; using System; using System.Drawing; +using OpenCvSharp; namespace BetterGenshinImpact.GameTask; @@ -11,7 +12,6 @@ namespace BetterGenshinImpact.GameTask; public class CaptureContent : IDisposable { public static readonly int MaxFrameIndexSecond = 60; - private Bitmap? SrcBitmap { get; } public int FrameIndex { get; } public double TimerInterval { get; } @@ -19,14 +19,13 @@ public class CaptureContent : IDisposable public ImageRegion CaptureRectArea { get; private set; } - public CaptureContent(Bitmap srcBitmap, int frameIndex, double interval) + public CaptureContent(Mat image, int frameIndex, double interval) { - SrcBitmap = srcBitmap; FrameIndex = frameIndex; TimerInterval = interval; var systemInfo = TaskContext.Instance().SystemInfo; - var gameCaptureRegion = systemInfo.DesktopRectArea.Derive(srcBitmap, systemInfo.CaptureAreaRect.X, systemInfo.CaptureAreaRect.Y); + var gameCaptureRegion = systemInfo.DesktopRectArea.Derive(image, systemInfo.CaptureAreaRect.X, systemInfo.CaptureAreaRect.Y); CaptureRectArea = gameCaptureRegion.DeriveTo1080P(); } @@ -42,6 +41,5 @@ public class CaptureContent : IDisposable public void Dispose() { CaptureRectArea.Dispose(); - SrcBitmap?.Dispose(); } -} +} \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/Common/TaskControl.cs b/BetterGenshinImpact/GameTask/Common/TaskControl.cs index a819adc2..8ca354f2 100644 --- a/BetterGenshinImpact/GameTask/Common/TaskControl.cs +++ b/BetterGenshinImpact/GameTask/Common/TaskControl.cs @@ -7,6 +7,7 @@ using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception; using BetterGenshinImpact.GameTask.Model.Area; using Fischless.GameCapture; using Microsoft.Extensions.Logging; +using OpenCvSharp; using Vanara.PInvoke; namespace BetterGenshinImpact.GameTask.Common; @@ -16,8 +17,6 @@ public class TaskControl public static ILogger Logger { get; } = App.GetLogger(); public static readonly SemaphoreSlim TaskSemaphore = new(1, 1); - - public static void CheckAndSleep(int millisecondsTimeout) @@ -177,88 +176,45 @@ public class TaskControl } } - public static Bitmap CaptureGameBitmap(IGameCapture? gameCapture) + public static Mat CaptureGameImage(IGameCapture? gameCapture) { - var bitmap = gameCapture?.Capture(); - // wgc 缓冲区设置的2 所以至少截图3次 - if (gameCapture?.Mode == CaptureModes.WindowsGraphicsCapture) - { - for (int i = 0; i < 2; i++) - { - bitmap = gameCapture?.Capture(); - Sleep(50); - } - } - - if (bitmap == null) + var image = gameCapture?.Capture(); + if (image == null) { Logger.LogWarning("截图失败!"); - // 重试5次 - for (var i = 0; i < 5; i++) + // 重试3次 + for (var i = 0; i < 3; i++) { - bitmap = gameCapture?.Capture(); - if (bitmap != null) + image = gameCapture?.Capture(); + if (image != null) { - return bitmap; + return image; } - + Sleep(30); } - + throw new Exception("尝试多次后,截图失败!"); } else { - return bitmap; + return image; } } - public static Bitmap? CaptureGameBitmapNoRetry(IGameCapture? gameCapture) + public static Mat? CaptureGameImageNoRetry(IGameCapture? gameCapture) { return gameCapture?.Capture(); } - // private static CaptureContent CaptureToContent(IGameCapture? gameCapture) - // { - // var bitmap = CaptureGameBitmap(gameCapture); - // return new CaptureContent(bitmap, 0, 0); - // } - // - // public static CaptureContent CaptureToContent() - // { - // return CaptureToContent(TaskTriggerDispatcher.GlobalGameCapture); - // } - - // public static ImageRegion CaptureToRectArea() - // { - // return CaptureToContent(TaskTriggerDispatcher.GlobalGameCapture).CaptureRectArea; - // } - - // /// - // /// 此方法 TaskDispatcher至少处于 DispatcherCaptureModeEnum.CacheCaptureWithTrigger 状态才能使用 - // /// - // /// - // [Obsolete] - // public static CaptureContent GetContentFromDispatcher() - // { - // return TaskTriggerDispatcher.Instance().GetLastCaptureContent(); - // } - - // /// - // /// 此方法 TaskDispatcher至少处于 DispatcherCaptureModeEnum.CacheCaptureWithTrigger 状态才能使用 - // /// - // /// - // public static ImageRegion GetRectAreaFromDispatcher() - // { - // return TaskTriggerDispatcher.Instance().GetLastCaptureContent().CaptureRectArea; - // } - /// /// 自动判断当前运行上下文中截图方式,并选择合适的截图方式返回 /// /// public static ImageRegion CaptureToRectArea(bool forceNew = false) { - return TaskTriggerDispatcher.Instance().CaptureToRectArea(forceNew); + var image =CaptureGameImage(TaskTriggerDispatcher.GlobalGameCapture); + var content = new CaptureContent(image, 0, 0); + return content.CaptureRectArea; } } diff --git a/BetterGenshinImpact/GameTask/Model/Area/DesktopRegion.cs b/BetterGenshinImpact/GameTask/Model/Area/DesktopRegion.cs index 1c4607cc..1d2255cf 100644 --- a/BetterGenshinImpact/GameTask/Model/Area/DesktopRegion.cs +++ b/BetterGenshinImpact/GameTask/Model/Area/DesktopRegion.cs @@ -3,6 +3,7 @@ using BetterGenshinImpact.GameTask.Model.Area.Converter; using BetterGenshinImpact.Helpers; using Fischless.WindowsInput; using System.Drawing; +using OpenCvSharp; namespace BetterGenshinImpact.GameTask.Model.Area; @@ -67,8 +68,8 @@ public class DesktopRegion : Region Simulation.SendInput.Mouse.MoveMouseBy((int)dx, (int)dy); } - public GameCaptureRegion Derive(Bitmap captureBitmap, int x, int y) + public GameCaptureRegion Derive(Mat captureMat, int x, int y) { - return new GameCaptureRegion(captureBitmap, x, y, this, new TranslationConverter(x, y)); + return new GameCaptureRegion(captureMat, x, y, this, new TranslationConverter(x, y)); } } diff --git a/BetterGenshinImpact/GameTask/Model/Area/GameCaptureRegion.cs b/BetterGenshinImpact/GameTask/Model/Area/GameCaptureRegion.cs index 4e497304..975b05df 100644 --- a/BetterGenshinImpact/GameTask/Model/Area/GameCaptureRegion.cs +++ b/BetterGenshinImpact/GameTask/Model/Area/GameCaptureRegion.cs @@ -11,7 +11,7 @@ namespace BetterGenshinImpact.GameTask.Model.Area; /// 游戏捕获区域类 /// 主要用于转换到遮罩窗口的坐标 /// -public class GameCaptureRegion(Bitmap bitmap, int initX, int initY, Region? owner = null, INodeConverter? converter = null, DrawContent? drawContent = null) : ImageRegion(bitmap, initX, initY, owner, converter, drawContent) +public class GameCaptureRegion(Mat mat, int initX, int initY, Region? owner = null, INodeConverter? converter = null, DrawContent? drawContent = null) : ImageRegion(mat, initX, initY, owner, converter, drawContent) { /// /// 在游戏捕获图像的坐标维度进行转换到遮罩窗口的坐标维度 diff --git a/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs b/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs index ffdd0c2a..47f57d98 100644 --- a/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs +++ b/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs @@ -76,7 +76,7 @@ public class ImageRegion : Region _srcBitmap = bitmap; } - public ImageRegion(Mat mat, int x, int y, Region? owner = null, INodeConverter? converter = null) : base(x, y, mat.Width, mat.Height, owner, converter) + public ImageRegion(Mat mat, int x, int y, Region? owner = null, INodeConverter? converter = null, DrawContent? drawContent = null) : base(x, y, mat.Width, mat.Height, owner, converter) { _srcMat = mat; } diff --git a/BetterGenshinImpact/GameTask/Model/BaseTaskParam.cs b/BetterGenshinImpact/GameTask/Model/BaseTaskParam.cs index 979b44b9..527f1a96 100644 --- a/BetterGenshinImpact/GameTask/Model/BaseTaskParam.cs +++ b/BetterGenshinImpact/GameTask/Model/BaseTaskParam.cs @@ -1,4 +1,4 @@ -using BetterGenshinImpact.GameTask.Model.Enum; + using System.Threading; namespace BetterGenshinImpact.GameTask.Model; diff --git a/BetterGenshinImpact/GameTask/Model/Enum/DispatcherCaptureModeEnum.cs b/BetterGenshinImpact/GameTask/Model/Enum/DispatcherCaptureModeEnum.cs deleted file mode 100644 index ec64160d..00000000 --- a/BetterGenshinImpact/GameTask/Model/Enum/DispatcherCaptureModeEnum.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace BetterGenshinImpact.GameTask.Model.Enum; - -/// -/// 调度器捕获模式, 影响以下内容: -/// 1. 是否缓存图像 -/// 2. 是否执行触发器 -/// -public enum DispatcherCaptureModeEnum -{ - // 正常运行调度器 - NormalTrigger, - - // 正常运行调度器,但不执行触发器,仅捕获并缓存图像模式 - OnlyCacheCapture, - - // 正常运行调度器,捕获并缓存图像模式,并执行触发器 - CacheCaptureWithTrigger, - - // -------------------------------------------- - // 下面两个模式无法直接设置,只能通过调度器的 StartTimer 和 StopTimer 方法来设置 - - // 停止运行整个调度器 - Stop, - - // 启动整个调度器 - Start, - - // -------------------------------------------- -} diff --git a/BetterGenshinImpact/GameTask/Model/Enum/DispatcherTimerOperationEnum.cs b/BetterGenshinImpact/GameTask/Model/Enum/DispatcherTimerOperationEnum.cs deleted file mode 100644 index 2a3033a9..00000000 --- a/BetterGenshinImpact/GameTask/Model/Enum/DispatcherTimerOperationEnum.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace BetterGenshinImpact.GameTask.Model.Enum; - -/// -/// 存在触发器运行的情况下,优先使用触发器的缓存图像 -/// 此枚举会影响调度器使用的 DispatcherCaptureModeEnum 模式 -/// -public enum DispatcherTimerOperationEnum -{ - // 关闭实时触发器,自己主动获取图像 - UseSelfCaptureImage, - - // 使用实时触发器的缓存图模式,但是不执行触发器 - UseCacheImage, - - // 使用实时触发器的缓存图模式,并执行触发器 - UseCacheImageWithTrigger, - - // 使用实时触发器的缓存图模式,并清空当前已有触发器,执行触发器 - // 清空触发器是为了让js脚本手动添加触发器 - UseCacheImageWithTriggerEmpty, - - // 不做任何操作 - None -} diff --git a/BetterGenshinImpact/GameTask/TaskRunner.cs b/BetterGenshinImpact/GameTask/TaskRunner.cs index 6662c1d7..caf29db0 100644 --- a/BetterGenshinImpact/GameTask/TaskRunner.cs +++ b/BetterGenshinImpact/GameTask/TaskRunner.cs @@ -1,6 +1,6 @@ using BetterGenshinImpact.Core.Script; using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception; -using BetterGenshinImpact.GameTask.Model.Enum; + using BetterGenshinImpact.View; using BetterGenshinImpact.View.Drawable; using Microsoft.Extensions.Logging; @@ -23,7 +23,7 @@ public class TaskRunner { private readonly ILogger _logger = App.GetLogger(); - private readonly DispatcherTimerOperationEnum _timerOperation = DispatcherTimerOperationEnum.None; + // private readonly DispatcherTimerOperationEnum _timerOperation = DispatcherTimerOperationEnum.None; private readonly string _name = string.Empty; @@ -31,11 +31,11 @@ public class TaskRunner { } - public TaskRunner(DispatcherTimerOperationEnum timerOperation) - { - _timerOperation = timerOperation; - } - + // public TaskRunner(DispatcherTimerOperationEnum timerOperation) + // { + // _timerOperation = timerOperation; + // } + /// /// 加锁并独立运行任务 /// @@ -134,27 +134,6 @@ public class TaskRunner var maskWindow = MaskWindow.Instance(); SystemControl.ActivateWindow(); maskWindow.Invoke(maskWindow.Show); - if (_timerOperation == DispatcherTimerOperationEnum.UseSelfCaptureImage) - { - Thread.Sleep(TaskContext.Instance().Config.TriggerInterval * 5); // 等待日志窗口被激活 - TaskTriggerDispatcher.Instance().SetCacheCaptureMode(DispatcherCaptureModeEnum.Stop); - } - else if (_timerOperation == DispatcherTimerOperationEnum.UseCacheImage) - { - TaskTriggerDispatcher.Instance().SetCacheCaptureMode(DispatcherCaptureModeEnum.OnlyCacheCapture); - Thread.Sleep(TaskContext.Instance().Config.TriggerInterval * 5); // 等待缓存图像 - } - else if (_timerOperation == DispatcherTimerOperationEnum.UseCacheImageWithTrigger) - { - TaskTriggerDispatcher.Instance().SetCacheCaptureMode(DispatcherCaptureModeEnum.CacheCaptureWithTrigger); - Thread.Sleep(TaskContext.Instance().Config.TriggerInterval * 5); // 等待缓存图像 - } - else if (_timerOperation == DispatcherTimerOperationEnum.UseCacheImageWithTriggerEmpty) - { - TaskTriggerDispatcher.Instance().SetCacheCaptureMode(DispatcherCaptureModeEnum.CacheCaptureWithTrigger); - TaskTriggerDispatcher.Instance().ClearTriggers(); - Thread.Sleep(TaskContext.Instance().Config.TriggerInterval * 5); // 等待缓存图像 - } } public void End() @@ -165,27 +144,6 @@ public class TaskRunner } VisionContext.Instance().DrawContent.ClearAll(); - if (_timerOperation == DispatcherTimerOperationEnum.UseSelfCaptureImage) - { - TaskTriggerDispatcher.Instance().SetCacheCaptureMode(DispatcherCaptureModeEnum.Start); - } - else if (_timerOperation is DispatcherTimerOperationEnum.UseCacheImage or DispatcherTimerOperationEnum.UseCacheImageWithTrigger or DispatcherTimerOperationEnum.UseCacheImageWithTriggerEmpty) - { - // 还原到原来的模式 - if (TaskContext.Instance().Config.CommonConfig.ScreenshotEnabled || TaskContext.Instance().Config.MacroConfig.CombatMacroEnabled) - { - TaskTriggerDispatcher.Instance().SetCacheCaptureMode(DispatcherCaptureModeEnum.CacheCaptureWithTrigger); - } - else - { - TaskTriggerDispatcher.Instance().SetCacheCaptureMode(DispatcherCaptureModeEnum.NormalTrigger); - } - - if (_timerOperation == DispatcherTimerOperationEnum.UseCacheImageWithTriggerEmpty) - { - TaskTriggerDispatcher.Instance().SetTriggers(GameTaskManager.LoadInitialTriggers()); - } - } } } diff --git a/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs b/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs index f7559ea6..c3afca4f 100644 --- a/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs +++ b/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs @@ -1,31 +1,18 @@ using BetterGenshinImpact.Core.Config; -using BetterGenshinImpact.GameTask.AutoDomain; -using BetterGenshinImpact.GameTask.AutoFight; -using BetterGenshinImpact.GameTask.AutoGeniusInvokation; -using BetterGenshinImpact.GameTask.AutoMusicGame; -using BetterGenshinImpact.GameTask.AutoSkip; -using BetterGenshinImpact.GameTask.AutoSkip.Model; -using BetterGenshinImpact.GameTask.AutoTrackPath; -using BetterGenshinImpact.GameTask.AutoWood; using BetterGenshinImpact.GameTask.Common; -using BetterGenshinImpact.GameTask.Model; -using BetterGenshinImpact.GameTask.Model.Area; -using BetterGenshinImpact.GameTask.Model.Enum; + using BetterGenshinImpact.Helpers; using BetterGenshinImpact.View; using Fischless.GameCapture; using Microsoft.Extensions.Logging; using OpenCvSharp; -using OpenCvSharp.Extensions; using System; using System.Collections.Generic; using System.Diagnostics; -using System.Drawing; -using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Threading; -using System.Threading.Tasks; +using Fischless.GameCapture.Graphics; using Vanara.PInvoke; namespace BetterGenshinImpact.GameTask @@ -49,19 +36,6 @@ namespace BetterGenshinImpact.GameTask private DateTime _prevManualGc = DateTime.MinValue; - /// - /// 捕获结果队列 - /// - private Bitmap _bitmap = new(10, 10); - - /// - /// 调度器捕获模式, 影响以下内容: - /// 1. 是否缓存图像 - /// 2. 是否执行触发器 - /// - private DispatcherCaptureModeEnum _dispatcherCacheCaptureMode = DispatcherCaptureModeEnum.NormalTrigger; - - private static readonly object _bitmapLocker = new(); private static readonly object _triggerListLocker = new(); public event EventHandler? UiTaskStopTickEvent; @@ -138,22 +112,20 @@ namespace BetterGenshinImpact.GameTask // 初始化触发器(一定要在任务上下文初始化完毕后使用) _triggers = GameTaskManager.LoadInitialTriggers(); + + if (GraphicsCapture.IsHdrEnabled(hWnd)) + { + _logger.LogError("游戏窗口在HDR模式下无法获取正常颜色的截图,请关闭HDR模式!"); + } // 启动截图 GameCapture.Start(hWnd, new Dictionary() { - { "useBitmapCache", TaskContext.Instance().Config.WgcUseBitmapCache }, { "autoFixWin11BitBlt", OsVersionHelper.IsWindows11_OrGreater && TaskContext.Instance().Config.AutoFixWin11BitBlt } } ); - - // 捕获模式初始化配置 - if (TaskContext.Instance().Config.CommonConfig.ScreenshotEnabled || TaskContext.Instance().Config.MacroConfig.CombatMacroEnabled) - { - _dispatcherCacheCaptureMode = DispatcherCaptureModeEnum.CacheCaptureWithTrigger; - } - + // 启动定时器 _frameIndex = 0; _timer.Interval = interval; @@ -187,11 +159,6 @@ namespace BetterGenshinImpact.GameTask } } - public System.Timers.Timer GetTimer() - { - return _timer; - } - public void Dispose() { Stop(); @@ -284,13 +251,13 @@ namespace BetterGenshinImpact.GameTask { // if (!_prevGameActive) // { - maskWindow.Invoke(() => + maskWindow.Invoke(() => + { + if (maskWindow.IsExist()) { - if (maskWindow.IsExist()) - { - maskWindow.Show(); - } - }); + maskWindow.Show(); + } + }); // } } @@ -305,10 +272,9 @@ namespace BetterGenshinImpact.GameTask // 帧序号自增 1分钟后归零(MaxFrameIndexSecond) _frameIndex = (_frameIndex + 1) % (int)(CaptureContent.MaxFrameIndexSecond * 1000d / _timer.Interval); - if (_dispatcherCacheCaptureMode == DispatcherCaptureModeEnum.NormalTrigger - && (_triggers == null || !_triggers.Exists(t => t.IsEnabled))) + if (_triggers == null || !_triggers.Exists(t => t.IsEnabled)) { - // Debug.WriteLine("没有可用的触发器且不处于仅截屏状态, 不再进行截屏"); + Debug.WriteLine("没有可用的触发器且不处于仅截屏状态, 不再进行截屏"); return; } @@ -323,11 +289,6 @@ namespace BetterGenshinImpact.GameTask return; } - if (IsOnlyCacheCapture(bitmap)) - { - return; - } - // 循环执行所有触发器 有独占状态的触发器的时候只执行独占触发器 var content = new CaptureContent(bitmap, _frameIndex, _timer.Interval); @@ -336,7 +297,6 @@ namespace BetterGenshinImpact.GameTask var exclusiveTrigger = _triggers!.FirstOrDefault(t => t is { IsEnabled: true, IsExclusive: true }); if (exclusiveTrigger != null) { - exclusiveTrigger.OnCapture(content); speedTimer.Record(exclusiveTrigger.Name); } @@ -350,13 +310,12 @@ namespace BetterGenshinImpact.GameTask foreach (var trigger in runningTriggers) { - trigger.OnCapture(content); speedTimer.Record(trigger.Name); } } } - + speedTimer.DebugPrint(); content.Dispose(); } @@ -413,118 +372,38 @@ namespace BetterGenshinImpact.GameTask return rect.Width == 0 || rect.Height == 0; } - /// - /// 是否仅缓存截图 - /// - /// - /// - private bool IsOnlyCacheCapture(Bitmap bitmap) - { - lock (_bitmapLocker) - { - if (_dispatcherCacheCaptureMode is DispatcherCaptureModeEnum.OnlyCacheCapture or DispatcherCaptureModeEnum.CacheCaptureWithTrigger) - { - _bitmap = new Bitmap(bitmap); - if (_dispatcherCacheCaptureMode == DispatcherCaptureModeEnum.OnlyCacheCapture) - { - return true; - } - } - - return false; - } - } - - public void SetCacheCaptureMode(DispatcherCaptureModeEnum mode) - { - if (mode is DispatcherCaptureModeEnum.Start) - { - this.StartTimer(); - } - else if (mode is DispatcherCaptureModeEnum.Stop) - { - this.StopTimer(); - } - else - { - _dispatcherCacheCaptureMode = mode; - } - } - - public DispatcherCaptureModeEnum GetCacheCaptureMode() - { - return _dispatcherCacheCaptureMode; - } - - public Bitmap GetLastCaptureBitmap() - { - lock (_bitmapLocker) - { - return new Bitmap(_bitmap); - } - } - - public CaptureContent GetLastCaptureContent() - { - var bitmap = GetLastCaptureBitmap(); - return new CaptureContent(bitmap, _frameIndex, _timer.Interval); - } - - public ImageRegion CaptureToRectArea(bool forceNew = false) - { - // 触发器启动的情况下优先使用触发器的截图 - if (!forceNew && _timer.Enabled && _dispatcherCacheCaptureMode is DispatcherCaptureModeEnum.OnlyCacheCapture or DispatcherCaptureModeEnum.CacheCaptureWithTrigger) - { - return GetLastCaptureContent().CaptureRectArea; - } - else - { - var bitmap = TaskControl.CaptureGameBitmap(GameCapture); - var content = new CaptureContent(bitmap, 0, 0); - return content.CaptureRectArea; - } - } - public void TakeScreenshot() { - if (_dispatcherCacheCaptureMode is DispatcherCaptureModeEnum.OnlyCacheCapture or DispatcherCaptureModeEnum.CacheCaptureWithTrigger) + try { - try + var path = Global.Absolute($@"log\screenshot\"); + if (!Directory.Exists(path)) { - var path = Global.Absolute($@"log\screenshot\"); - if (!Directory.Exists(path)) - { - Directory.CreateDirectory(path); - } - - var bitmap = GetLastCaptureBitmap(); - var name = $@"{DateTime.Now:yyyyMMddHHmmssffff}.png"; - var savePath = Global.Absolute($@"log\screenshot\{name}"); - - if (TaskContext.Instance().Config.CommonConfig.ScreenshotUidCoverEnabled) - { - var mat = bitmap.ToMat(); - var rect = TaskContext.Instance().Config.MaskWindowConfig.UidCoverRect; - mat.Rectangle(rect, Scalar.White, -1); - Cv2.ImWrite(savePath, mat); - } - else - { - bitmap.Save(savePath, ImageFormat.Png); - } - - _logger.LogInformation("截图已保存: {Name}", name); + Directory.CreateDirectory(path); } - catch (Exception e) + + var bitmap = TaskControl.CaptureGameImage(GameCapture); + var name = $@"{DateTime.Now:yyyyMMddHHmmssffff}.png"; + var savePath = Global.Absolute($@"log\screenshot\{name}"); + + if (TaskContext.Instance().Config.CommonConfig.ScreenshotUidCoverEnabled) { - _logger.LogError("截图保存失败: {Message}", e.Message); - _logger.LogDebug("截图保存失败: {StackTrace}", e.StackTrace); + var rect = TaskContext.Instance().Config.MaskWindowConfig.UidCoverRect; + bitmap.Rectangle(rect, Scalar.White, -1); + Cv2.ImWrite(savePath, bitmap); } + else + { + Cv2.ImWrite(savePath, bitmap); + } + + _logger.LogInformation("截图已保存: {Name}", name); } - else + catch (Exception e) { - _logger.LogWarning("当前不处于截图模式,无法保存截图"); + _logger.LogError("截图保存失败: {Message}", e.Message); + _logger.LogDebug("截图保存失败: {StackTrace}", e.StackTrace); } } } -} +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/Notification/NotificationService.cs b/BetterGenshinImpact/Service/Notification/NotificationService.cs index 952087eb..d5329966 100644 --- a/BetterGenshinImpact/Service/Notification/NotificationService.cs +++ b/BetterGenshinImpact/Service/Notification/NotificationService.cs @@ -1,3 +1,8 @@ +using BetterGenshinImpact.Service.Notification.Model; +using BetterGenshinImpact.Service.Notifier; +using BetterGenshinImpact.Service.Notifier.Exception; +using BetterGenshinImpact.Service.Notifier.Interface; +using Microsoft.Extensions.Hosting; using System; using System.Drawing; using System.Net.Http; @@ -13,6 +18,8 @@ using BetterGenshinImpact.Service.Notifier.Exception; using BetterGenshinImpact.Service.Notifier.Interface; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using System.Text.Json; +using OpenCvSharp.Extensions; namespace BetterGenshinImpact.Service.Notification; @@ -170,10 +177,10 @@ public class NotificationService : IHostedService { if (TaskContext.Instance().Config.NotificationConfig.IncludeScreenShot) { - var bitmap = TaskControl.CaptureGameBitmapNoRetry(TaskTriggerDispatcher.GlobalGameCapture); + var bitmap = TaskControl.CaptureGameImageNoRetry(TaskTriggerDispatcher.GlobalGameCapture); if (bitmap != null) { - notificationData.Screenshot = (Bitmap)bitmap.Clone(); + notificationData.Screenshot = bitmap.ToBitmap(); } } } diff --git a/BetterGenshinImpact/Service/ScriptService.cs b/BetterGenshinImpact/Service/ScriptService.cs index e22a65cf..3d3915b7 100644 --- a/BetterGenshinImpact/Service/ScriptService.cs +++ b/BetterGenshinImpact/Service/ScriptService.cs @@ -2,7 +2,7 @@ using BetterGenshinImpact.Core.Script.Group; using BetterGenshinImpact.Core.Script.Project; using BetterGenshinImpact.GameTask; -using BetterGenshinImpact.GameTask.Model.Enum; + using BetterGenshinImpact.Service.Interface; using BetterGenshinImpact.ViewModel.Pages; using Microsoft.Extensions.Logging; @@ -23,6 +23,7 @@ namespace BetterGenshinImpact.Service; public partial class ScriptService : IScriptService { private readonly ILogger _logger = App.GetLogger(); + private static bool IsCurrentHourEqual(string input) { // 尝试将输入字符串转换为整数 @@ -41,52 +42,51 @@ public partial class ScriptService : IScriptService // 如果输入非数字或不合法,返回 false return false; } + public async Task RunMulti(IEnumerable projectList, string? groupName = null) { groupName ??= "默认"; - var hasTimer = false; - var list = ReloadScriptProjects(projectList, ref hasTimer); + var list = ReloadScriptProjects(projectList); - // 针对JS 脚本,检查是否包含定时器操作 - var jsProjects = ExtractJsProjects(list); - if (!hasTimer && jsProjects.Count > 0) - { - var codeList = await ReadCodeList(jsProjects); - hasTimer = HasTimerOperation(codeList); - } + // // 针对JS 脚本,检查是否包含定时器操作 + // var jsProjects = ExtractJsProjects(list); + // if (!hasTimer && jsProjects.Count > 0) + // { + // var codeList = await ReadCodeList(jsProjects); + // hasTimer = HasTimerOperation(codeList); + // } // 没启动时候,启动截图器 await StartGameTask(); if (!string.IsNullOrEmpty(groupName)) { - if (hasTimer) - { - _logger.LogInformation("配置组 {Name} 包含实时任务操作调用", groupName); - } + // if (hasTimer) + // { + // _logger.LogInformation("配置组 {Name} 包含实时任务操作调用", groupName); + // } _logger.LogInformation("配置组 {Name} 加载完成,共{Cnt}个脚本,开始执行", groupName, list.Count); } - var timerOperation = hasTimer ? DispatcherTimerOperationEnum.UseCacheImageWithTriggerEmpty : DispatcherTimerOperationEnum.UseSelfCaptureImage; - + // var timerOperation = hasTimer ? DispatcherTimerOperationEnum.UseCacheImageWithTriggerEmpty : DispatcherTimerOperationEnum.UseSelfCaptureImage; + Notify.Event(NotificationEvent.GroupStart).Success($"配置组{groupName}启动"); - - await new TaskRunner(timerOperation) + + await new TaskRunner() .RunThreadAsync(async () => { var stopwatch = new Stopwatch(); foreach (var project in list) { - if (project.GroupInfo is { Config.PathingConfig.Enabled: true } && IsCurrentHourEqual(project.GroupInfo.Config.PathingConfig.SkipDuring)) { _logger.LogInformation($"{project.Name}任务已到禁止执行时段,将跳过!"); continue; } - + if (project.Status != "Enabled") { _logger.LogInformation("脚本 {Name} 状态为禁用,跳过执行", project.Name); @@ -103,24 +103,21 @@ public partial class ScriptService : IScriptService { try { - if (hasTimer) - { - TaskTriggerDispatcher.Instance().ClearTriggers(); - } + TaskTriggerDispatcher.Instance().ClearTriggers(); + _logger.LogInformation("------------------------------"); stopwatch.Reset(); stopwatch.Start(); await ExecuteProject(project); - + //多次执行时及时中断 if (project.GroupInfo is { Config.PathingConfig.Enabled: true } && IsCurrentHourEqual(project.GroupInfo.Config.PathingConfig.SkipDuring)) { _logger.LogInformation($"{project.Name}任务已到禁止执行时段,将跳过!"); break; } - } catch (NormalEndException e) { @@ -144,7 +141,6 @@ public partial class ScriptService : IScriptService _logger.LogInformation("→ 脚本执行结束: {Name}, 耗时: {Minutes}分{Seconds:0.000}秒", project.Name, elapsedTime.Hours * 60 + elapsedTime.Minutes, elapsedTime.TotalSeconds % 60); _logger.LogInformation("------------------------------"); - } await Task.Delay(2000); @@ -152,14 +148,18 @@ public partial class ScriptService : IScriptService } }); + // 还原定时器 + TaskTriggerDispatcher.Instance().SetTriggers(GameTaskManager.LoadInitialTriggers()); + if (!string.IsNullOrEmpty(groupName)) { _logger.LogInformation("配置组 {Name} 执行结束", groupName); } + Notify.Event(NotificationEvent.GroupEnd).Success($"配置组{groupName}结束"); } - private List ReloadScriptProjects(IEnumerable projectList, ref bool hasTimer) + private List ReloadScriptProjects(IEnumerable projectList) { var list = new List(); foreach (var project in projectList) @@ -181,14 +181,14 @@ public partial class ScriptService : IScriptService var newProject = ScriptGroupProject.BuildPathingProject(project.Name, project.FolderName); CopyProjectProperties(project, newProject); list.Add(newProject); - hasTimer = true; + // hasTimer = true; } else if (project.Type == "Shell") { var newProject = ScriptGroupProject.BuildShellProject(project.Name); CopyProjectProperties(project, newProject); list.Add(newProject); - hasTimer = true; + // hasTimer = true; } } @@ -204,23 +204,22 @@ public partial class ScriptService : IScriptService target.GroupInfo = source.GroupInfo; } - private List ExtractJsProjects(List list) - { - var jsProjects = new List(); - foreach (var project in list) - { - if (project is { Type: "Javascript", Project: not null }) - { - jsProjects.Add(project.Project); - } - } - - return jsProjects; - } + // private List ExtractJsProjects(List list) + // { + // var jsProjects = new List(); + // foreach (var project in list) + // { + // if (project is { Type: "Javascript", Project: not null }) + // { + // jsProjects.Add(project.Project); + // } + // } + // + // return jsProjects; + // } private async Task ExecuteProject(ScriptGroupProject project) { - if (project.Type == "Javascript") { if (project.Project == null) @@ -241,7 +240,8 @@ public partial class ScriptService : IScriptService _logger.LogInformation("→ 开始执行地图追踪任务: {Name}", project.Name); await project.Run(); } - else if (project.Type == "Shell"){ + else if (project.Type == "Shell") + { _logger.LogInformation("→ 开始执行shell: {Name}", project.Name); await project.Run(); } diff --git a/BetterGenshinImpact/View/CaptureTestWindow.xaml.cs b/BetterGenshinImpact/View/CaptureTestWindow.xaml.cs index 366563b5..5ab0fdde 100644 --- a/BetterGenshinImpact/View/CaptureTestWindow.xaml.cs +++ b/BetterGenshinImpact/View/CaptureTestWindow.xaml.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Windows; using System.Windows.Media; using BetterGenshinImpact.Helpers; +using OpenCvSharp.WpfExtensions; using Wpf.Ui.Violeta.Controls; namespace BetterGenshinImpact.View; @@ -49,7 +50,6 @@ public partial class CaptureTestWindow : Window _capture.Start(hWnd, new Dictionary() { - { "useBitmapCache", TaskContext.Instance().Config.WgcUseBitmapCache }, { "autoFixWin11BitBlt", OsVersionHelper.IsWindows11 && TaskContext.Instance().Config.AutoFixWin11BitBlt } } ); @@ -72,7 +72,7 @@ public partial class CaptureTestWindow : Window _captureCount++; sw.Reset(); sw.Start(); - DisplayCaptureResultImage.Source = bitmap.ToBitmapImage(); + DisplayCaptureResultImage.Source = bitmap.ToBitmapSource(); sw.Stop(); Debug.WriteLine("转换耗时:" + sw.ElapsedMilliseconds); _transferTime += sw.ElapsedMilliseconds; diff --git a/BetterGenshinImpact/View/Pages/HomePage.xaml b/BetterGenshinImpact/View/Pages/HomePage.xaml index 90053173..d2c156a7 100644 --- a/BetterGenshinImpact/View/Pages/HomePage.xaml +++ b/BetterGenshinImpact/View/Pages/HomePage.xaml @@ -176,7 +176,7 @@ Margin="0,0,36,0" Text="{Binding Config.TriggerInterval, Mode=TwoWay}" /> - + diff --git a/BetterGenshinImpact/ViewModel/Pages/CommonSettingsPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/CommonSettingsPageViewModel.cs index dba9eb06..17c84354 100644 --- a/BetterGenshinImpact/ViewModel/Pages/CommonSettingsPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/CommonSettingsPageViewModel.cs @@ -1,7 +1,6 @@ using System.Collections.ObjectModel; using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.GameTask; -using BetterGenshinImpact.GameTask.Model.Enum; using BetterGenshinImpact.Service.Interface; using BetterGenshinImpact.View.Pages; using CommunityToolkit.Mvvm.ComponentModel; @@ -157,13 +156,6 @@ public partial class CommonSettingsPageViewModel : ViewModel [RelayCommand] public void OnSwitchTakenScreenshotEnabled() { - if (Config.CommonConfig.ScreenshotEnabled) - { - if (TaskTriggerDispatcher.Instance().GetCacheCaptureMode() == DispatcherCaptureModeEnum.NormalTrigger) - { - TaskTriggerDispatcher.Instance().SetCacheCaptureMode(DispatcherCaptureModeEnum.CacheCaptureWithTrigger); - } - } } [RelayCommand] diff --git a/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs index 52206e70..393d5ca0 100644 --- a/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs @@ -2,7 +2,6 @@ using BetterGenshinImpact.Core.Recorder; using BetterGenshinImpact.Core.Script; using BetterGenshinImpact.GameTask; -using BetterGenshinImpact.GameTask.Model.Enum; using BetterGenshinImpact.Model; using BetterGenshinImpact.Service.Interface; using BetterGenshinImpact.View.Windows; @@ -125,7 +124,7 @@ public partial class KeyMouseRecordPageViewModel : ViewModel { var s = await File.ReadAllTextAsync(path); - await new TaskRunner(DispatcherTimerOperationEnum.UseSelfCaptureImage) + await new TaskRunner() .RunThreadAsync(async () => await KeyMouseMacroPlayer.PlayMacro(s, CancellationContext.Instance.Cts.Token)); } catch (Exception e) diff --git a/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs index f4c01070..c0504042 100644 --- a/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs @@ -15,7 +15,7 @@ using BetterGenshinImpact.Core.Script.Group; using BetterGenshinImpact.GameTask; using BetterGenshinImpact.GameTask.Common.Element.Assets; using BetterGenshinImpact.GameTask.Common.Job; -using BetterGenshinImpact.GameTask.Model.Enum; + using BetterGenshinImpact.Helpers; using BetterGenshinImpact.Service; using BetterGenshinImpact.Service.Notification; @@ -213,7 +213,7 @@ public partial class OneDragonFlowViewModel : ViewModel // 没启动的时候先启动 await ScriptService.StartGameTask(); - await new TaskRunner(DispatcherTimerOperationEnum.UseSelfCaptureImage) + await new TaskRunner() .RunThreadAsync(async () => { Notify.Event(NotificationEvent.DragonStart).Success("一条龙启动"); diff --git a/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs index af7e7409..f91db6a4 100644 --- a/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs @@ -23,7 +23,7 @@ using System.Threading.Tasks; using Windows.System; using BetterGenshinImpact.GameTask.AutoFishing; using BetterGenshinImpact.GameTask.Common.Element.Assets; -using BetterGenshinImpact.GameTask.Model.Enum; + using BetterGenshinImpact.Helpers; using Wpf.Ui; using Wpf.Ui.Controls; @@ -183,7 +183,7 @@ public partial class TaskSettingsPageViewModel : ViewModel } SwitchAutoGeniusInvokationEnabled = true; - await new TaskRunner(DispatcherTimerOperationEnum.UseSelfCaptureImage) + await new TaskRunner() .RunSoloTaskAsync(new AutoGeniusInvokationTask(new GeniusInvokationTaskParam(content))); SwitchAutoGeniusInvokationEnabled = false; } @@ -219,7 +219,7 @@ public partial class TaskSettingsPageViewModel : ViewModel public async Task OnSwitchAutoWood() { SwitchAutoWoodEnabled = true; - await new TaskRunner(DispatcherTimerOperationEnum.UseSelfCaptureImage) + await new TaskRunner() .RunSoloTaskAsync(new AutoWoodTask(new WoodTaskParam(AutoWoodRoundNum, AutoWoodDailyMaxCount))); SwitchAutoWoodEnabled = false; } @@ -241,7 +241,7 @@ public partial class TaskSettingsPageViewModel : ViewModel var param = new AutoFightParam(path, Config.AutoFightConfig); SwitchAutoFightEnabled = true; - await new TaskRunner(DispatcherTimerOperationEnum.UseCacheImageWithTrigger) + await new TaskRunner() .RunSoloTaskAsync(new AutoFightTask(param)); SwitchAutoFightEnabled = false; } @@ -261,7 +261,7 @@ public partial class TaskSettingsPageViewModel : ViewModel } SwitchAutoDomainEnabled = true; - await new TaskRunner(DispatcherTimerOperationEnum.UseCacheImage) + await new TaskRunner() .RunSoloTaskAsync(new AutoDomainTask(new AutoDomainParam(AutoDomainRoundNum, path))); SwitchAutoDomainEnabled = false; } @@ -376,7 +376,7 @@ public partial class TaskSettingsPageViewModel : ViewModel private async Task OnSwitchAutoMusicGame() { SwitchAutoMusicGameEnabled = true; - await new TaskRunner(DispatcherTimerOperationEnum.UseSelfCaptureImage) + await new TaskRunner() .RunSoloTaskAsync(new AutoMusicGameTask(new AutoMusicGameParam())); SwitchAutoMusicGameEnabled = false; } @@ -391,7 +391,7 @@ public partial class TaskSettingsPageViewModel : ViewModel private async Task OnSwitchAutoAlbum() { SwitchAutoAlbumEnabled = true; - await new TaskRunner(DispatcherTimerOperationEnum.UseSelfCaptureImage) + await new TaskRunner() .RunSoloTaskAsync(new AutoAlbumTask(new AutoMusicGameParam())); SwitchAutoAlbumEnabled = false; } @@ -401,7 +401,7 @@ public partial class TaskSettingsPageViewModel : ViewModel { SwitchAutoFishingEnabled = true; var param = AutoFishingTaskParam.BuildFromConfig(TaskContext.Instance().Config.AutoFishingConfig, SaveScreenshotOnKeyTick); - await new TaskRunner(DispatcherTimerOperationEnum.UseSelfCaptureImage) + await new TaskRunner() .RunSoloTaskAsync(new AutoFishingTask(param)); SwitchAutoFishingEnabled = false; } diff --git a/Fischless.GameCapture/BitBlt/BitBltCapture.cs b/Fischless.GameCapture/BitBlt/BitBltCapture.cs index de379344..6024ad2e 100644 --- a/Fischless.GameCapture/BitBlt/BitBltCapture.cs +++ b/Fischless.GameCapture/BitBlt/BitBltCapture.cs @@ -1,4 +1,6 @@ using System.Diagnostics; +using System.Runtime.InteropServices; +using OpenCvSharp; using Vanara.PInvoke; namespace Fischless.GameCapture.BitBlt; @@ -7,6 +9,9 @@ public class BitBltCapture : IGameCapture { private nint _hWnd; + private static readonly object LockObject = new object(); + + public CaptureModes Mode => CaptureModes.BitBlt; public bool IsCapturing { get; private set; } @@ -26,36 +31,79 @@ public class BitBltCapture : IGameCapture } } - public Bitmap? Capture() + public Mat? Capture() { if (_hWnd == IntPtr.Zero) { return null; } - try + // 加锁 + lock (LockObject) { - User32.GetClientRect(_hWnd, out var windowRect); - int x = default, y = default; - var width = windowRect.right - windowRect.left; - var height = windowRect.bottom - windowRect.top; + User32.SafeReleaseHDC hdcSrc = User32.SafeReleaseHDC.Null; + Gdi32.SafeHDC hdcDest = Gdi32.SafeHDC.Null; + Gdi32.SafeHBITMAP hBitmap = Gdi32.SafeHBITMAP.Null; + try + { + User32.GetClientRect(_hWnd, out var windowRect); + int x = 0, y = 0; + var width = windowRect.right - windowRect.left; + var height = windowRect.bottom - windowRect.top; - Bitmap bitmap = new(width, height); - using System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bitmap); - var hdcDest = g.GetHdc(); - var hdcSrc = User32.GetDC(_hWnd == IntPtr.Zero ? User32.GetDesktopWindow() : _hWnd); - Gdi32.StretchBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, width, height, Gdi32.RasterOperationMode.SRCCOPY); - g.ReleaseHdc(); - Gdi32.DeleteDC(hdcDest); - Gdi32.DeleteDC(hdcSrc); - return bitmap; - } - catch (Exception e) - { - Debug.WriteLine(e); - } + hdcSrc = User32.GetDC(_hWnd == IntPtr.Zero ? User32.GetDesktopWindow() : _hWnd); + hdcDest = Gdi32.CreateCompatibleDC(hdcSrc); - return null!; + var bmi = new Gdi32.BITMAPINFO + { + bmiHeader = new Gdi32.BITMAPINFOHEADER + { + biSize = (uint)Marshal.SizeOf(), + biWidth = width, + biHeight = -height, // Top-down image + biPlanes = 1, + biBitCount = 32, + biCompression = 0, // BI_RGB + biSizeImage = 0 + } + }; + + nint bits = 0; + hBitmap = Gdi32.CreateDIBSection(hdcDest, bmi, Gdi32.DIBColorMode.DIB_RGB_COLORS, out bits, IntPtr.Zero, 0); + var oldBitmap = Gdi32.SelectObject(hdcDest, hBitmap); + + Gdi32.StretchBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, width, height, Gdi32.RasterOperationMode.SRCCOPY); + + var mat = new Mat(height, width, MatType.CV_8UC4, bits); + Mat bgrMat = new Mat(); + Cv2.CvtColor(mat, bgrMat, ColorConversionCodes.BGRA2BGR); + + Gdi32.SelectObject(hdcDest, oldBitmap); + return bgrMat; + } + catch (Exception e) + { + Debug.WriteLine(e); + return null!; + } + finally + { + if (hBitmap != Gdi32.SafeHBITMAP.Null) + { + Gdi32.DeleteObject(hBitmap); + } + + if (hdcDest != Gdi32.SafeHDC.Null) + { + Gdi32.DeleteDC(hdcDest); + } + + if (_hWnd != IntPtr.Zero) + { + User32.ReleaseDC(_hWnd, hdcSrc); + } + } + } } public void Stop() @@ -63,4 +111,4 @@ public class BitBltCapture : IGameCapture _hWnd = IntPtr.Zero; IsCapturing = false; } -} +} \ No newline at end of file diff --git a/Fischless.GameCapture/DwmSharedSurface/SharedSurfaceCapture.cs b/Fischless.GameCapture/DwmSharedSurface/SharedSurfaceCapture.cs index b5f59828..53f2b6c8 100644 --- a/Fischless.GameCapture/DwmSharedSurface/SharedSurfaceCapture.cs +++ b/Fischless.GameCapture/DwmSharedSurface/SharedSurfaceCapture.cs @@ -3,6 +3,8 @@ using Fischless.GameCapture.Graphics.Helpers; using SharpDX.Direct3D11; using SharpDX.DXGI; using System.Diagnostics; +using OpenCvSharp; +using OpenCvSharp.Extensions; using Vanara.PInvoke; using Device = SharpDX.Direct3D11.Device; @@ -11,6 +13,7 @@ namespace Fischless.GameCapture.DwmSharedSurface public class SharedSurfaceCapture : IGameCapture { private nint _hWnd; + private static readonly object LockObject = new object(); private Device? _d3dDevice; public void Dispose() => Stop(); @@ -64,34 +67,45 @@ namespace Fischless.GameCapture.DwmSharedSurface return region; } - public Bitmap? Capture() + public Mat? Capture() { if (_hWnd == nint.Zero) { return null; } - - NativeMethods.DwmGetDxSharedSurface(_hWnd, out var phSurface, out _, out _, out _, out _); - if (phSurface == nint.Zero) + lock (LockObject) { - return null; - } + NativeMethods.DwmGetDxSharedSurface(_hWnd, out var phSurface, out _, out _, out _, out _); + if (phSurface == nint.Zero) + { + return null; + } - return ToBitmap(phSurface); + + if (_d3dDevice == null) + { + Debug.WriteLine("D3Device is null."); + return null; + } + + using var surfaceTexture = _d3dDevice.OpenSharedResource(phSurface); + using var stagingTexture = CreateStagingTexture(surfaceTexture, _d3dDevice); + var mat = stagingTexture.CreateMat(_d3dDevice, surfaceTexture, _region); + if (mat == null) + { + return null; + } + var bgrMat = new Mat(); + Cv2.CvtColor(mat, bgrMat, ColorConversionCodes.BGRA2BGR); + return bgrMat; + } } + - private Bitmap? ToBitmap(nint phSurface) + private Texture2D CreateStagingTexture(Texture2D surfaceTexture, Device device) { - if (_d3dDevice == null) - { - Debug.WriteLine("D3Device is null."); - return null; - } - - using var surfaceTexture = _d3dDevice.OpenSharedResource(phSurface); - - var staging = new Texture2D(_d3dDevice, new Texture2DDescription + return new Texture2D(device, new Texture2DDescription { Width = _region == null ? surfaceTexture.Description.Width : _region.Value.Right - _region.Value.Left, Height = _region == null ? surfaceTexture.Description.Height : _region.Value.Bottom - _region.Value.Top, @@ -104,9 +118,6 @@ namespace Fischless.GameCapture.DwmSharedSurface CpuAccessFlags = CpuAccessFlags.Read, OptionFlags = ResourceOptionFlags.None }); - - - return staging.CreateBitmap(_d3dDevice, surfaceTexture, _region); } public void Stop() diff --git a/Fischless.GameCapture/Fischless.GameCapture.csproj b/Fischless.GameCapture/Fischless.GameCapture.csproj index 7b895bae..1e832bb0 100644 --- a/Fischless.GameCapture/Fischless.GameCapture.csproj +++ b/Fischless.GameCapture/Fischless.GameCapture.csproj @@ -19,6 +19,8 @@ + + \ No newline at end of file diff --git a/Fischless.GameCapture/Graphics/GraphicsCapture.cs b/Fischless.GameCapture/Graphics/GraphicsCapture.cs index 6113635a..a36f4a16 100644 --- a/Fischless.GameCapture/Graphics/GraphicsCapture.cs +++ b/Fischless.GameCapture/Graphics/GraphicsCapture.cs @@ -5,6 +5,11 @@ using Vanara.PInvoke; using Windows.Foundation.Metadata; using Windows.Graphics.Capture; using Windows.Graphics.DirectX; +using Windows.Graphics.DirectX.Direct3D11; +using OpenCvSharp; +using SharpDX.DXGI; +using Device = SharpDX.Direct3D11.Device; +using MapFlags = SharpDX.Direct3D11.MapFlags; namespace Fischless.GameCapture.Graphics; @@ -14,15 +19,27 @@ public class GraphicsCapture : IGameCapture private Direct3D11CaptureFramePool _captureFramePool = null!; private GraphicsCaptureItem _captureItem = null!; + private GraphicsCaptureSession _captureSession = null!; + private IDirect3DDevice _d3dDevice = null!; + public CaptureModes Mode => CaptureModes.WindowsGraphicsCapture; public bool IsCapturing { get; private set; } private ResourceRegion? _region; - private bool _useBitmapCache = false; - private Bitmap? _currentBitmap; + // HDR相关 + private bool _isHdrEnabled; + private DirectXPixelFormat _pixelFormat = DirectXPixelFormat.B8G8R8A8UIntNormalized; + + + // 最新帧的存储 + private Mat? _latestFrame; + private readonly ReaderWriterLockSlim _frameAccessLock = new(); + + // 用于获取帧数据的临时纹理和暂存资源 + private Texture2D? _stagingTexture; public void Dispose() => Stop(); @@ -41,24 +58,31 @@ public class GraphicsCapture : IGameCapture throw new InvalidOperationException("Failed to create capture item."); } - _captureItem.Closed += CaptureItemOnClosed; - var device = Direct3D11Helper.CreateDevice(); + // 创建D3D设备 + _d3dDevice = Direct3D11Helper.CreateDevice(); - _captureFramePool = Direct3D11CaptureFramePool.Create(device, DirectXPixelFormat.B8G8R8A8UIntNormalized, 2, + // 检测HDR状态 + _isHdrEnabled = IsHdrEnabled(_hWnd); + _pixelFormat = _isHdrEnabled ? DirectXPixelFormat.R16G16B16A16Float : DirectXPixelFormat.B8G8R8A8UIntNormalized; + + // 创建帧池 + _captureFramePool = Direct3D11CaptureFramePool.Create( + _d3dDevice, + _pixelFormat, + 2, _captureItem.Size); - if (settings != null && settings.TryGetValue("useBitmapCache", out object? value) && (bool)value) - { - _useBitmapCache = true; - _captureFramePool.FrameArrived += OnFrameArrived; - } + + _captureItem.Closed += CaptureItemOnClosed; + _captureFramePool.FrameArrived += OnFrameArrived; _captureSession = _captureFramePool.CreateCaptureSession(_captureItem); if (ApiInformation.IsPropertyPresent("Windows.Graphics.Capture.GraphicsCaptureSession", "IsCursorCaptureEnabled")) { _captureSession.IsCursorCaptureEnabled = false; } + if (ApiInformation.IsWriteablePropertyPresent("Windows.Graphics.Capture.GraphicsCaptureSession", "IsBorderRequired")) { _captureSession.IsBorderRequired = false; @@ -98,55 +122,165 @@ public class GraphicsCapture : IGameCapture return region; } - private void OnFrameArrived(Direct3D11CaptureFramePool sender, object args) + public static bool IsHdrEnabled(nint hWnd) { - using var frame = _captureFramePool?.TryGetNextFrame(); - - if (frame != null) + try { - var b = frame.ToBitmap(_region); - if (b != null) + var hdc = User32.GetDC(hWnd); + if (hdc != IntPtr.Zero) { - _currentBitmap = b; + int bitsPerPixel = Gdi32.GetDeviceCaps(hdc, Gdi32.DeviceCap.BITSPIXEL); + User32.ReleaseDC(hWnd, hdc); + + // 如果位深大于等于32位,认为支持HDR + return bitsPerPixel >= 32; } + + return false; + } + catch + { + return false; } } - public Bitmap? Capture() + private Texture2D CreateStagingTexture(Direct3D11CaptureFrame frame, Device device) { - if (_hWnd == IntPtr.Zero) + // 创建可以用于CPU读取的暂存纹理 + var textureDesc = new Texture2DDescription { - return null; + CpuAccessFlags = CpuAccessFlags.Read, + BindFlags = BindFlags.None, + Format = _isHdrEnabled ? Format.R16G16B16A16_Float : Format.B8G8R8A8_UNorm, + Width = _region == null ? frame.ContentSize.Width : _region.Value.Right - _region.Value.Left, + Height = _region == null ? frame.ContentSize.Height : _region.Value.Bottom - _region.Value.Top, + OptionFlags = ResourceOptionFlags.None, + MipLevels = 1, + ArraySize = 1, + SampleDescription = { Count = 1, Quality = 0 }, + Usage = ResourceUsage.Staging + }; + + return new Texture2D(device, textureDesc); + } + + private void OnFrameArrived(Direct3D11CaptureFramePool sender, object args) + { + using var frame = sender.TryGetNextFrame(); + if (frame == null) + { + return; } - if (!_useBitmapCache) + var frameSize = _captureItem.Size; + + // 检查帧大小是否变化 // 不会被访问到的代码 + if (frameSize.Width != frame.ContentSize.Width || + frameSize.Height != frame.ContentSize.Height) { - try - { - using var frame = _captureFramePool?.TryGetNextFrame(); + frameSize = frame.ContentSize; + _captureFramePool.Recreate( + _d3dDevice, + _pixelFormat, + 2, + frameSize + ); + _stagingTexture = null; + } - if (frame == null) - { - return null; - } + // 从捕获的帧创建一个可以被访问的纹理 + using var surfaceTexture = Direct3D11Helper.CreateSharpDXTexture2D(frame.Surface); + var d3dDevice = surfaceTexture.Device; - return frame.ToBitmap(_region); - } - catch (Exception e) - { - Debug.WriteLine(e); - } + _stagingTexture ??= CreateStagingTexture(frame, d3dDevice); + var stagingTexture = _stagingTexture; - return null; + // 将捕获的纹理复制到暂存纹理 + if (_region != null) + { + d3dDevice.ImmediateContext.CopySubresourceRegion(surfaceTexture, 0, _region, stagingTexture, 0); } else { - if (_currentBitmap == null) + d3dDevice.ImmediateContext.CopyResource(surfaceTexture, stagingTexture); + } + + + // 映射纹理以便CPU读取 + var dataBox = d3dDevice.ImmediateContext.MapSubresource( + stagingTexture, + 0, + MapMode.Read, + MapFlags.None); + + try + { + // 创建一个新的Mat + var newFrame = new Mat(stagingTexture.Description.Height, stagingTexture.Description.Width, + _isHdrEnabled ? MatType.MakeType(7, 4) : MatType.CV_8UC4, dataBox.DataPointer); + + // 如果是HDR,进行HDR到SDR的转换 + if (_isHdrEnabled) + { + // rgb -> bgr + newFrame = ConvertHdrToSdr(newFrame); + } + + // 使用写锁更新最新帧 + _frameAccessLock.EnterWriteLock(); + try + { + // 释放之前的帧 + _latestFrame?.Dispose(); + // 克隆新帧以保持对其的引用(因为dataBox.DataPointer将被释放) + _latestFrame = newFrame.Clone(); + } + finally + { + newFrame.Dispose(); + _frameAccessLock.ExitWriteLock(); + } + } + finally + { + // 取消映射纹理 + d3dDevice.ImmediateContext.UnmapSubresource(surfaceTexture, 0); + } + } + + private static Mat ConvertHdrToSdr(Mat hdrMat) + { + // 创建一个目标 8UC4 Mat + Mat sdkMat = new Mat(hdrMat.Size(), MatType.CV_8UC4); + + // 将 32FC4 缩放到 0-255 范围并转换为 8UC4 + // 注意:这种简单缩放可能不会保留 HDR 的所有细节 + hdrMat.ConvertTo(sdkMat, MatType.CV_8UC4, 255.0); + + // 将 HDR 的 RGB 通道转换为 BGR + Cv2.CvtColor(sdkMat, sdkMat, ColorConversionCodes.RGBA2BGRA); + + return sdkMat; + } + + public Mat? Capture() + { + // 使用读锁获取最新帧 + _frameAccessLock.EnterReadLock(); + try + { + // 如果没有可用帧则返回null + if (_latestFrame == null) { return null; } - return (Bitmap)_currentBitmap.Clone(); + // 返回最新帧的副本(这里我们必须克隆,因为Mat是不线程安全的) + return _latestFrame.Clone(); + } + finally + { + _frameAccessLock.ExitReadLock(); } } @@ -157,9 +291,25 @@ public class GraphicsCapture : IGameCapture _captureSession = null!; _captureFramePool = null!; _captureItem = null!; + _stagingTexture?.Dispose(); + _d3dDevice?.Dispose(); _hWnd = IntPtr.Zero; IsCapturing = false; + + // 释放最新帧 + _frameAccessLock.EnterWriteLock(); + try + { + _latestFrame?.Dispose(); + _latestFrame = null; + } + finally + { + _frameAccessLock.ExitWriteLock(); + } + + _frameAccessLock.Dispose(); } private void CaptureItemOnClosed(GraphicsCaptureItem sender, object args) diff --git a/Fischless.GameCapture/Graphics/Helpers/Texture2DExtensions.cs b/Fischless.GameCapture/Graphics/Helpers/Texture2DExtensions.cs index 0feca9c5..f3f94a4e 100644 --- a/Fischless.GameCapture/Graphics/Helpers/Texture2DExtensions.cs +++ b/Fischless.GameCapture/Graphics/Helpers/Texture2DExtensions.cs @@ -4,6 +4,7 @@ using SharpDX.DXGI; using System.Diagnostics; using System.Drawing.Imaging; using Windows.Graphics.Capture; +using OpenCvSharp; namespace Fischless.GameCapture.Graphics.Helpers; @@ -67,4 +68,40 @@ public static class Texture2DExtensions staging.Dispose(); } } + + public static Mat? CreateMat(this Texture2D staging, SharpDX.Direct3D11.Device d3dDevice, Texture2D surfaceTexture, ResourceRegion? region = null) + { + try + { + // Copy data + if (region != null) + { + d3dDevice.ImmediateContext.CopySubresourceRegion(surfaceTexture, 0, region, staging, 0); + } + else + { + d3dDevice.ImmediateContext.CopyResource(surfaceTexture, staging); + } + + // 映射纹理以便CPU读取 + var dataBox = d3dDevice.ImmediateContext.MapSubresource( + staging, + 0, + MapMode.Read, + SharpDX.Direct3D11.MapFlags.None); + + var mat = new Mat(staging.Description.Height, staging.Description.Width, MatType.CV_8UC4, dataBox.DataPointer); + return mat; + } + catch (Exception e) + { + Debug.WriteLine("Failed to copy texture to mat."); + Debug.WriteLine(e.StackTrace); + return null; + } + finally + { + staging.Dispose(); + } + } } \ No newline at end of file diff --git a/Fischless.GameCapture/IGameCapture.cs b/Fischless.GameCapture/IGameCapture.cs index d6922cf2..ffb85923 100644 --- a/Fischless.GameCapture/IGameCapture.cs +++ b/Fischless.GameCapture/IGameCapture.cs @@ -1,4 +1,6 @@ -namespace Fischless.GameCapture; +using OpenCvSharp; + +namespace Fischless.GameCapture; public interface IGameCapture : IDisposable { @@ -7,7 +9,7 @@ public interface IGameCapture : IDisposable public void Start(nint hWnd, Dictionary? settings = null); - public Bitmap? Capture(); + public Mat? Capture(); public void Stop(); } diff --git a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.FishBite.cs b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.FishBite.cs index 5d42179a..56e24ff5 100644 --- a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.FishBite.cs +++ b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.FishBite.cs @@ -6,6 +6,7 @@ using System.Drawing; using BehaviourTree.Composites; using BehaviourTree.FluentBuilder; using Microsoft.Extensions.Time.Testing; +using OpenCvSharp; namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests { @@ -20,7 +21,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests public void FishBite_ShouldSuccess(string screenshot1080p) { // - Bitmap bitmap = new Bitmap(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); + Mat bitmap = new Mat(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); var imageRegion = new GameCaptureRegion(bitmap, 0, 0, new DesktopRegion(new FakeMouseSimulator()), converter: new ScaleConverter(1d), drawContent: new FakeDrawContent()); // @@ -39,7 +40,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests public void FishBite_Tree_Timeout_ShouldSuccess(string screenshot1080pCheckThrowRod, string screenshot1080pFishBite) { // - Bitmap bitmap = new Bitmap(@$"..\..\..\Assets\AutoFishing\{screenshot1080pCheckThrowRod}"); + Mat bitmap = new Mat(@$"..\..\..\Assets\AutoFishing\{screenshot1080pCheckThrowRod}"); var imageRegion = new GameCaptureRegion(bitmap, 0, 0, new DesktopRegion(new FakeMouseSimulator()), converter: new ScaleConverter(1d), drawContent: new FakeDrawContent()); DateTimeOffset dateTime = new DateTimeOffset(2025, 2, 26, 16, 13, 54, 285, TimeSpan.FromHours(8)); @@ -70,7 +71,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests // fakeTimeProvider.SetUtcNow(dateTime.AddSeconds(16)); - bitmap = new Bitmap(@$"..\..\..\Assets\AutoFishing\{screenshot1080pFishBite}"); + bitmap = new Mat(@$"..\..\..\Assets\AutoFishing\{screenshot1080pFishBite}"); imageRegion = new GameCaptureRegion(bitmap, 0, 0, new DesktopRegion(new FakeMouseSimulator()), converter: new ScaleConverter(1d), drawContent: new FakeDrawContent()); // diff --git a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.GetFishBoxArea.cs b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.GetFishBoxArea.cs index 5b8f13de..ed005f51 100644 --- a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.GetFishBoxArea.cs +++ b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.GetFishBoxArea.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using System.Drawing; using BetterGenshinImpact.Core.Recognition.ONNX.YOLO; using Microsoft.Extensions.Time.Testing; +using OpenCvSharp; namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests { @@ -25,7 +26,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests public void GetFishBoxArea_ShouldSuccess(string screenshot1080p) { // - Bitmap bitmap = new Bitmap(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); + Mat bitmap = new Mat(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); var imageRegion = new GameCaptureRegion(bitmap, 0, 0, drawContent: new FakeDrawContent()); var blackboard = new Blackboard(null, sleep: i => { }); @@ -47,7 +48,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests public void GetFishBoxArea_ShouldFail(string screenshot1080p) { // - Bitmap bitmap = new Bitmap(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); + Mat bitmap = new Mat(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); var imageRegion = new GameCaptureRegion(bitmap, 0, 0, drawContent: new FakeDrawContent()); var blackboard = new Blackboard(null, sleep: i => { }); diff --git a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.GetFishpond.cs b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.GetFishpond.cs index e77aed18..5e9d4603 100644 --- a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.GetFishpond.cs +++ b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.GetFishpond.cs @@ -10,6 +10,7 @@ using BetterGenshinImpact.GameTask.Model.Area; using BetterGenshinImpact.Core.Config; using Compunet.YoloV8; using BetterGenshinImpact.GameTask.AutoFishing.Model; +using OpenCvSharp; namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests { @@ -25,7 +26,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests public void GetFishpondTest_VariousFishExist_ShouldSuccess(string screenshot1080p, IEnumerable fishNames) { // - Bitmap bitmap = new Bitmap(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); + Mat bitmap = new Mat(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); var imageRegion = new GameCaptureRegion(bitmap, 0, 0, drawContent: new FakeDrawContent()); var predictor = YoloV8Builder.CreateDefaultBuilder().UseOnnxModel(Global.Absolute(@"Assets\Model\Fish\bgi_fish.onnx")).Build(); @@ -53,7 +54,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests public void GetFishpondTest_AllIgnored_ShouldBeRunning(string screenshot1080p, IEnumerable chooseBaitfailures, IEnumerable throwRodNoTargetFishfailures) { // - Bitmap bitmap = new Bitmap(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); + Mat bitmap = new Mat(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); var imageRegion = new GameCaptureRegion(bitmap, 0, 0, drawContent: new FakeDrawContent()); var predictor = YoloV8Builder.CreateDefaultBuilder().UseOnnxModel(Global.Absolute(@"Assets\Model\Fish\bgi_fish.onnx")).Build(); @@ -88,7 +89,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests public void GetFishpondTest_FishCount_ShouldSuccess(string screenshot1080p, string fishName, int count) { // - Bitmap bitmap = new Bitmap(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); + Mat bitmap = new Mat(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); var imageRegion = new GameCaptureRegion(bitmap, 0, 0, drawContent: new FakeDrawContent()); var predictor = YoloV8Builder.CreateDefaultBuilder().UseOnnxModel(Global.Absolute(@"Assets\Model\Fish\bgi_fish.onnx")).Build(); diff --git a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.ThrowRod.cs b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.ThrowRod.cs index 0e5b3ff4..3277ee01 100644 --- a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.ThrowRod.cs +++ b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.ThrowRod.cs @@ -12,6 +12,7 @@ using System.Drawing; using BetterGenshinImpact.Core.Config; using Compunet.YoloV8; using Microsoft.Extensions.Time.Testing; +using OpenCvSharp; namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests { @@ -26,7 +27,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests public void ThrowRodTest_VariousFish_ShouldSuccess(string screenshot1080p, string selectedBaitName) { // - Bitmap bitmap = new Bitmap(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); + Mat bitmap = new Mat(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); var imageRegion = new GameCaptureRegion(bitmap, 0, 0, new DesktopRegion(new FakeMouseSimulator()), converter: new ScaleConverter(1d), drawContent: new FakeDrawContent()); var predictor = YoloV8Builder.CreateDefaultBuilder().UseOnnxModel(Global.Absolute(@"Assets\Model\Fish\bgi_fish.onnx")).Build(); @@ -56,7 +57,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests public void ThrowRodTest_VariousFish_ShouldFail(string screenshot1080p, string selectedBaitName) { // - Bitmap bitmap = new Bitmap(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); + Mat bitmap = new Mat(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); var imageRegion = new GameCaptureRegion(bitmap, 0, 0, new DesktopRegion(new FakeMouseSimulator()), converter: new ScaleConverter(1d), drawContent: new FakeDrawContent()); var predictor = YoloV8Builder.CreateDefaultBuilder().UseOnnxModel(Global.Absolute(@"Assets\Model\Fish\bgi_fish.onnx")).Build(); @@ -85,7 +86,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests public void ThrowRodTest_NoBaitFish_ShouldFail(string screenshot1080p, string selectedBaitName) { // - Bitmap bitmap = new Bitmap(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); + Mat bitmap = new Mat(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); var imageRegion = new GameCaptureRegion(bitmap, 0, 0, new DesktopRegion(new FakeMouseSimulator()), converter: new ScaleConverter(1d), drawContent: new FakeDrawContent()); var predictor = YoloV8Builder.CreateDefaultBuilder().UseOnnxModel(Global.Absolute(@"Assets\Model\Fish\bgi_fish.onnx")).Build(); @@ -126,7 +127,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests public void ThrowRodTest_Target_ShouldBeTheLeftOne() { // - Bitmap bitmap = new Bitmap(@$"..\..\..\Assets\AutoFishing\202503082114541115.png"); + Mat bitmap = new Mat(@$"..\..\..\Assets\AutoFishing\202503082114541115.png"); var imageRegion = new GameCaptureRegion(bitmap, 0, 0, new DesktopRegion(new FakeMouseSimulator()), converter: new ScaleConverter(1d), drawContent: new FakeDrawContent()); var predictor = YoloV8Builder.CreateDefaultBuilder().UseOnnxModel(Global.Absolute(@"Assets\Model\Fish\bgi_fish.onnx")).Build(); @@ -147,7 +148,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests Assert.Equal(blackboard.fishpond.Fishes.OrderBy(f => f.Rect.X).First(), actual); // - bitmap = new Bitmap(@$"..\..\..\Assets\AutoFishing\202503082114560489.png"); + bitmap = new Mat(@$"..\..\..\Assets\AutoFishing\202503082114560489.png"); imageRegion = new GameCaptureRegion(bitmap, 0, 0, new DesktopRegion(new FakeMouseSimulator()), converter: new ScaleConverter(1d), drawContent: new FakeDrawContent()); // @@ -168,7 +169,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests public void ThrowRodTest_NoTarget_ShouldFail(string screenshot1080p) { // - Bitmap bitmap = new Bitmap(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); + Mat bitmap = new Mat(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); var imageRegion = new GameCaptureRegion(bitmap, 0, 0, new DesktopRegion(new FakeMouseSimulator()), converter: new ScaleConverter(1d), drawContent: new FakeDrawContent()); var predictor = YoloV8Builder.CreateDefaultBuilder().UseOnnxModel(Global.Absolute(@"Assets\Model\Fish\bgi_fish.onnx")).Build();