mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-05-04 23:55:54 +08:00
提供老版本的稳定 BitBlt 功能
This commit is contained in:
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Text;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.AutoFishing
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using BetterGenshinImpact.GameTask.Model.Area;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using Fischless.GameCapture;
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask;
|
||||
@@ -19,7 +20,7 @@ public class CaptureContent : IDisposable
|
||||
|
||||
public ImageRegion CaptureRectArea { get; private set; }
|
||||
|
||||
public CaptureContent(Mat image, int frameIndex, double interval)
|
||||
public CaptureContent(CaptureImageRes image, int frameIndex, double interval)
|
||||
{
|
||||
FrameIndex = frameIndex;
|
||||
TimerInterval = interval;
|
||||
|
||||
@@ -17,13 +17,13 @@ public class TaskControl
|
||||
public static ILogger Logger { get; } = App.GetLogger<TaskControl>();
|
||||
|
||||
public static readonly SemaphoreSlim TaskSemaphore = new(1, 1);
|
||||
|
||||
|
||||
|
||||
|
||||
public static void CheckAndSleep(int millisecondsTimeout)
|
||||
{
|
||||
TrySuspend();
|
||||
CheckAndActivateGameWindow();
|
||||
|
||||
|
||||
Thread.Sleep(millisecondsTimeout);
|
||||
}
|
||||
|
||||
@@ -33,10 +33,10 @@ public class TaskControl
|
||||
{
|
||||
TrySuspend();
|
||||
CheckAndActivateGameWindow();
|
||||
|
||||
}, TimeSpan.FromSeconds(1), 100);
|
||||
Thread.Sleep(millisecondsTimeout);
|
||||
}
|
||||
|
||||
private static bool IsKeyPressed(User32.VK key)
|
||||
{
|
||||
// 获取按键状态
|
||||
@@ -45,6 +45,7 @@ public class TaskControl
|
||||
// 检查高位是否为 1(表示按键被按下)
|
||||
return (state & 0x8000) != 0;
|
||||
}
|
||||
|
||||
public static void TrySuspend()
|
||||
{
|
||||
var first = true;
|
||||
@@ -65,6 +66,7 @@ public class TaskControl
|
||||
Simulation.SendInput.Keyboard.KeyUp(key);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.LogWarning("快捷键触发暂停,等待解除");
|
||||
foreach (var item in RunnerContext.Instance.SuspendableDictionary)
|
||||
{
|
||||
@@ -76,6 +78,7 @@ public class TaskControl
|
||||
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
//从暂停中解除
|
||||
if (isSuspend)
|
||||
{
|
||||
@@ -97,12 +100,12 @@ public class TaskControl
|
||||
throw new RetryException("当前获取焦点的窗口不是原神");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var count = 0;
|
||||
//未激活则尝试恢复窗口
|
||||
while (!SystemControl.IsGenshinImpactActiveByProcess())
|
||||
{
|
||||
if (count>=10 && count%10==0)
|
||||
if (count >= 10 && count % 10 == 0)
|
||||
{
|
||||
Logger.LogInformation("多次尝试未恢复,尝试最小化后激活窗口!");
|
||||
SystemControl.MinimizeAndActivateWindow(TaskContext.Instance().GameHandle);
|
||||
@@ -112,6 +115,7 @@ public class TaskControl
|
||||
Logger.LogInformation("当前获取焦点的窗口不是原神,尝试恢复窗口");
|
||||
SystemControl.FocusWindow(TaskContext.Instance().GameHandle);
|
||||
}
|
||||
|
||||
count++;
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
@@ -135,10 +139,9 @@ public class TaskControl
|
||||
{
|
||||
throw new NormalEndException("取消自动任务");
|
||||
}
|
||||
|
||||
TrySuspend();
|
||||
CheckAndActivateGameWindow();
|
||||
|
||||
|
||||
}, TimeSpan.FromSeconds(1), 100);
|
||||
Thread.Sleep(millisecondsTimeout);
|
||||
if (ct.IsCancellationRequested)
|
||||
@@ -165,9 +168,9 @@ public class TaskControl
|
||||
{
|
||||
throw new NormalEndException("取消自动任务");
|
||||
}
|
||||
|
||||
TrySuspend();
|
||||
CheckAndActivateGameWindow();
|
||||
|
||||
}, TimeSpan.FromSeconds(1), 100);
|
||||
await Task.Delay(millisecondsTimeout, ct);
|
||||
if (ct is { IsCancellationRequested: true })
|
||||
@@ -176,7 +179,7 @@ public class TaskControl
|
||||
}
|
||||
}
|
||||
|
||||
public static Mat CaptureGameImage(IGameCapture? gameCapture)
|
||||
public static CaptureImageRes CaptureGameImage(IGameCapture? gameCapture)
|
||||
{
|
||||
var image = gameCapture?.Capture();
|
||||
if (image == null)
|
||||
@@ -190,10 +193,10 @@ public class TaskControl
|
||||
{
|
||||
return image;
|
||||
}
|
||||
|
||||
|
||||
Sleep(30);
|
||||
}
|
||||
|
||||
|
||||
throw new Exception("尝试多次后,截图失败!");
|
||||
}
|
||||
else
|
||||
@@ -201,8 +204,8 @@ public class TaskControl
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
public static Mat? CaptureGameImageNoRetry(IGameCapture? gameCapture)
|
||||
|
||||
public static CaptureImageRes? CaptureGameImageNoRetry(IGameCapture? gameCapture)
|
||||
{
|
||||
return gameCapture?.Capture();
|
||||
}
|
||||
@@ -213,8 +216,8 @@ public class TaskControl
|
||||
/// <returns></returns>
|
||||
public static ImageRegion CaptureToRectArea(bool forceNew = false)
|
||||
{
|
||||
var image =CaptureGameImage(TaskTriggerDispatcher.GlobalGameCapture);
|
||||
var image = CaptureGameImage(TaskTriggerDispatcher.GlobalGameCapture);
|
||||
var content = new CaptureContent(image, 0, 0);
|
||||
return content.CaptureRectArea;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using BetterGenshinImpact.GameTask.Model.Area.Converter;
|
||||
using BetterGenshinImpact.Helpers;
|
||||
using Fischless.WindowsInput;
|
||||
using System.Drawing;
|
||||
using Fischless.GameCapture;
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.Model.Area;
|
||||
@@ -68,7 +69,7 @@ public class DesktopRegion : Region
|
||||
Simulation.SendInput.Mouse.MoveMouseBy((int)dx, (int)dy);
|
||||
}
|
||||
|
||||
public GameCaptureRegion Derive(Mat captureMat, int x, int y)
|
||||
public GameCaptureRegion Derive(CaptureImageRes captureMat, int x, int y)
|
||||
{
|
||||
return new GameCaptureRegion(captureMat, x, y, this, new TranslationConverter(x, y));
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using BetterGenshinImpact.View.Drawable;
|
||||
using OpenCvSharp;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using Fischless.GameCapture;
|
||||
using Size = OpenCvSharp.Size;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.Model.Area;
|
||||
@@ -11,8 +12,19 @@ namespace BetterGenshinImpact.GameTask.Model.Area;
|
||||
/// 游戏捕获区域类
|
||||
/// 主要用于转换到遮罩窗口的坐标
|
||||
/// </summary>
|
||||
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)
|
||||
public class GameCaptureRegion : ImageRegion
|
||||
{
|
||||
public GameCaptureRegion(Mat mat, int initX, int initY, Region? owner = null, INodeConverter? converter = null,
|
||||
DrawContent? drawContent = null) : base(mat, initX, initY, owner, converter, drawContent)
|
||||
{
|
||||
}
|
||||
|
||||
public GameCaptureRegion(CaptureImageRes image, int initX, int initY, Region? owner = null,
|
||||
INodeConverter? converter = null, DrawContent? drawContent = null) : base(image, initX, initY, owner, converter, drawContent)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 在游戏捕获图像的坐标维度进行转换到遮罩窗口的坐标维度
|
||||
/// </summary>
|
||||
@@ -48,6 +60,7 @@ public class GameCaptureRegion(Mat mat, int initX, int initY, Region? owner = nu
|
||||
{
|
||||
drawable.Pen = pen;
|
||||
}
|
||||
|
||||
return drawable;
|
||||
}
|
||||
|
||||
@@ -66,6 +79,7 @@ public class GameCaptureRegion(Mat mat, int initX, int initY, Region? owner = nu
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
var scale = Width / 1920d;
|
||||
|
||||
var newMat = new Mat();
|
||||
@@ -109,7 +123,7 @@ public class GameCaptureRegion(Mat mat, int initX, int initY, Region? owner = nu
|
||||
var (dx, dy) = deltaFunc(new Size(captureAreaRect.Width, captureAreaRect.Height), assetScale);
|
||||
DesktopRegion.DesktopRegionMoveBy(dx, dy);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 静态方法,输入1080P下的坐标,方法会自动转换到当前游戏捕获区域大小下的坐标并点击
|
||||
/// </summary>
|
||||
@@ -126,4 +140,4 @@ public class GameCaptureRegion(Mat mat, int initX, int initY, Region? owner = nu
|
||||
// 1080P坐标 转换到实际游戏窗口坐标
|
||||
GameRegionMove((_, scale) => (cx * scale, cy * scale));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Fischless.GameCapture;
|
||||
using Point = OpenCvSharp.Point;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.Model.Area;
|
||||
@@ -77,6 +78,23 @@ public class ImageRegion : Region
|
||||
_srcMat = mat;
|
||||
}
|
||||
|
||||
public ImageRegion(CaptureImageRes image, int x, int y, Region? owner = null, INodeConverter? converter = null,
|
||||
DrawContent? drawContent = null) : base(x, y, image.Width, image.Height, owner, converter, drawContent)
|
||||
{
|
||||
if (image.Bitmap != null)
|
||||
{
|
||||
_srcBitmap = image.Bitmap;
|
||||
}
|
||||
else if (image.Mat != null)
|
||||
{
|
||||
_srcMat = image.Mat;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("ImageRegion的构造函数参数错误");
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasImage()
|
||||
{
|
||||
return _srcBitmap != null || _srcMat != null;
|
||||
|
||||
@@ -386,19 +386,24 @@ namespace BetterGenshinImpact.GameTask
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
|
||||
var bitmap = TaskControl.CaptureGameImage(GameCapture);
|
||||
var image = TaskControl.CaptureGameImage(GameCapture);
|
||||
var mat = image.ForceGetMat();
|
||||
if (mat == null)
|
||||
{
|
||||
_logger.LogInformation("截图失败,未获取到图像");
|
||||
return;
|
||||
}
|
||||
var name = $@"{DateTime.Now:yyyyMMddHHmmssffff}.png";
|
||||
var savePath = Global.Absolute($@"log\screenshot\{name}");
|
||||
|
||||
if (TaskContext.Instance().Config.CommonConfig.ScreenshotUidCoverEnabled)
|
||||
{
|
||||
var rect = TaskContext.Instance().Config.MaskWindowConfig.UidCoverRect;
|
||||
bitmap.Rectangle(rect, Scalar.White, -1);
|
||||
Cv2.ImWrite(savePath, bitmap);
|
||||
mat.Rectangle(rect, Scalar.White, -1);
|
||||
Cv2.ImWrite(savePath, mat);
|
||||
}
|
||||
else
|
||||
{
|
||||
Cv2.ImWrite(savePath, bitmap);
|
||||
Cv2.ImWrite(savePath, mat);
|
||||
}
|
||||
|
||||
_logger.LogInformation("截图已保存: {Name}", name);
|
||||
|
||||
27
BetterGenshinImpact/Helpers/Extensions/EnumExtensions.cs
Normal file
27
BetterGenshinImpact/Helpers/Extensions/EnumExtensions.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using BetterGenshinImpact.Model;
|
||||
|
||||
namespace BetterGenshinImpact.Helpers.Extensions;
|
||||
|
||||
public static class EnumExtensions
|
||||
{
|
||||
public static string GetDescription(this Enum value)
|
||||
{
|
||||
return value.GetType()
|
||||
.GetField(value.ToString())
|
||||
?.GetCustomAttributes(typeof(DescriptionAttribute), false)
|
||||
.Cast<DescriptionAttribute>()
|
||||
.FirstOrDefault()
|
||||
?.Description ?? value.ToString();
|
||||
}
|
||||
|
||||
public static IEnumerable<EnumItem<T>> ToEnumItems<T>() where T : Enum
|
||||
{
|
||||
return Enum.GetValues(typeof(T))
|
||||
.Cast<T>()
|
||||
.Select(EnumItem<T>.Create);
|
||||
}
|
||||
}
|
||||
21
BetterGenshinImpact/Model/EnumItem.cs
Normal file
21
BetterGenshinImpact/Model/EnumItem.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using BetterGenshinImpact.Helpers.Extensions;
|
||||
|
||||
namespace BetterGenshinImpact.Model;
|
||||
|
||||
public class EnumItem<T> where T : Enum
|
||||
{
|
||||
public T Value { get; set; }
|
||||
public string DisplayName { get; set; }
|
||||
public string EnumName => Value.ToString();
|
||||
|
||||
// 提供一个静态工厂方法来创建实例
|
||||
public static EnumItem<T> Create(T value)
|
||||
{
|
||||
return new EnumItem<T>
|
||||
{
|
||||
Value = value,
|
||||
DisplayName = value.GetDescription() // 使用扩展方法获取Description
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -406,8 +406,19 @@ public class NotificationService : IHostedService, IDisposable
|
||||
|
||||
try
|
||||
{
|
||||
var bitmap = TaskControl.CaptureGameImageNoRetry(TaskTriggerDispatcher.GlobalGameCapture);
|
||||
if (bitmap != null) notificationData.Screenshot = bitmap.ToBitmap();
|
||||
var image = TaskControl.CaptureGameImageNoRetry(TaskTriggerDispatcher.GlobalGameCapture);
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
if (image.Bitmap != null)
|
||||
{
|
||||
notificationData.Screenshot = image.Bitmap;
|
||||
}
|
||||
else if (image.Mat != null)
|
||||
{
|
||||
notificationData.Screenshot = image.Mat.ToBitmap();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -61,11 +61,12 @@ public partial class CaptureTestWindow : Window
|
||||
{
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
var bitmap = _capture?.Capture();
|
||||
var image = _capture?.Capture();
|
||||
sw.Stop();
|
||||
Debug.WriteLine("截图耗时:" + sw.ElapsedMilliseconds);
|
||||
_captureTime += sw.ElapsedMilliseconds;
|
||||
|
||||
var bitmap = image?.ForceGetBitmap();
|
||||
if (bitmap != null)
|
||||
{
|
||||
Debug.WriteLine($"Bitmap:{bitmap.Width}x{bitmap.Height}");
|
||||
|
||||
@@ -134,14 +134,16 @@
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="如果可用的话,推荐选择 BitBlt,问题较少"
|
||||
Text="推荐选择 BitBlt(稳定),问题较少"
|
||||
TextWrapping="Wrap" />
|
||||
<ComboBox Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,36,0"
|
||||
ItemsSource="{Binding ModeNames, Mode=OneWay}"
|
||||
SelectedItem="{Binding Config.CaptureMode, Mode=TwoWay}">
|
||||
DisplayMemberPath="DisplayName"
|
||||
SelectedValuePath="EnumName"
|
||||
SelectedValue="{Binding Config.CaptureMode, Mode=TwoWay}">
|
||||
<b:Interaction.Triggers>
|
||||
<b:EventTrigger EventName="SelectionChanged">
|
||||
<b:InvokeCommandAction Command="{Binding CaptureModeDropDownChangedCommand}" CommandParameter="GeniusInvocation" />
|
||||
|
||||
@@ -15,22 +15,30 @@ using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Fischless.GameCapture;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using Windows.System;
|
||||
using BetterGenshinImpact.GameTask.AutoFishing;
|
||||
using BetterGenshinImpact.Helpers.Extensions;
|
||||
using BetterGenshinImpact.Model;
|
||||
using Wpf.Ui.Controls;
|
||||
|
||||
namespace BetterGenshinImpact.ViewModel.Pages;
|
||||
|
||||
public partial class HomePageViewModel : ViewModel
|
||||
{
|
||||
|
||||
[ObservableProperty]
|
||||
private string[] _modeNames = GameCaptureFactory.ModeNames();
|
||||
private IEnumerable<EnumItem<CaptureModes>> _modeNames = EnumExtensions.ToEnumItems<CaptureModes>();
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _selectedMode = CaptureModes.BitBlt.ToString();
|
||||
@@ -66,11 +74,14 @@ public partial class HomePageViewModel : ViewModel
|
||||
Config = configService.Get();
|
||||
ReadGameInstallPath();
|
||||
|
||||
|
||||
// WindowsGraphicsCapture 只支持 Win10 18362 及以上的版本 (Windows 10 version 1903 or later)
|
||||
// https://github.com/babalae/better-genshin-impact/issues/394
|
||||
if (!OsVersionHelper.IsWindows10_1903_OrGreater)
|
||||
{
|
||||
_modeNames = _modeNames.Where(x => x != CaptureModes.WindowsGraphicsCapture.ToString()).ToArray();
|
||||
// 删除 _modeNames 中的 CaptureModes.WindowsGraphicsCapture
|
||||
_modeNames = _modeNames.Where(x => x.EnumName != CaptureModes.WindowsGraphicsCapture.ToString()).ToList();
|
||||
|
||||
// DirectML 是在 Windows 10 版本 1903 和 Windows SDK 的相应版本中引入的。
|
||||
// https://learn.microsoft.com/zh-cn/windows/ai/directml/dml
|
||||
_inferenceDeviceTypes = _inferenceDeviceTypes.Where(x => x != "GPU_DirectML").ToArray();
|
||||
|
||||
@@ -18,7 +18,7 @@ public class BitBltCapture : IGameCapture
|
||||
|
||||
public void Dispose() => Stop();
|
||||
|
||||
public Mat? Capture() => Capture(false);
|
||||
public CaptureImageRes? Capture() => Capture(false);
|
||||
|
||||
public void Start(nint hWnd, Dictionary<string, object>? settings = null)
|
||||
{
|
||||
@@ -111,7 +111,7 @@ public class BitBltCapture : IGameCapture
|
||||
/// </summary>
|
||||
/// <param name="recursive">递归标志</param>
|
||||
/// <returns>截图</returns>
|
||||
private Mat? Capture(bool recursive)
|
||||
private CaptureImageRes? Capture(bool recursive)
|
||||
{
|
||||
if (_hWnd == IntPtr.Zero)
|
||||
{
|
||||
@@ -140,13 +140,13 @@ public class BitBltCapture : IGameCapture
|
||||
{
|
||||
// 成功截图
|
||||
_lastCaptureFailed = false;
|
||||
return result;
|
||||
return CaptureImageRes.BuildNullable(result);
|
||||
}
|
||||
else if (result is null)
|
||||
{
|
||||
if (_lastCaptureFailed) return result; // 这不是首次失败,不再进行尝试
|
||||
if (_lastCaptureFailed) return CaptureImageRes.BuildNullable(result); // 这不是首次失败,不再进行尝试
|
||||
_lastCaptureFailed = true; // 设置失败标志
|
||||
if (recursive) return result; // 已设置递归标志,说明也不是首次失败
|
||||
if (recursive) return CaptureImageRes.BuildNullable(result); // 已设置递归标志,说明也不是首次失败
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
||||
72
Fischless.GameCapture/BitBlt/BitBltOldCapture.cs
Normal file
72
Fischless.GameCapture/BitBlt/BitBltOldCapture.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System.Diagnostics;
|
||||
using Vanara.PInvoke;
|
||||
|
||||
namespace Fischless.GameCapture.BitBlt;
|
||||
|
||||
public class BitBltOldCapture : IGameCapture
|
||||
{
|
||||
private nint _hWnd;
|
||||
|
||||
public CaptureModes Mode => CaptureModes.BitBltOld;
|
||||
|
||||
public static object LockObject { get; } = new();
|
||||
|
||||
public bool IsCapturing { get; private set; }
|
||||
|
||||
public void Dispose() => Stop();
|
||||
|
||||
public void Start(nint hWnd, Dictionary<string, object>? settings = null)
|
||||
{
|
||||
_hWnd = hWnd;
|
||||
IsCapturing = true;
|
||||
if (settings != null && settings.TryGetValue("autoFixWin11BitBlt", out var value))
|
||||
{
|
||||
if (value is true)
|
||||
{
|
||||
BitBltRegistryHelper.SetDirectXUserGlobalSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CaptureImageRes? 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;
|
||||
|
||||
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 CaptureImageRes.BuildNullable(bitmap);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine(e);
|
||||
}
|
||||
|
||||
return null!;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_hWnd = IntPtr.Zero;
|
||||
IsCapturing = false;
|
||||
}
|
||||
}
|
||||
76
Fischless.GameCapture/CaptureImageRes.cs
Normal file
76
Fischless.GameCapture/CaptureImageRes.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using OpenCvSharp;
|
||||
using OpenCvSharp.Extensions;
|
||||
|
||||
namespace Fischless.GameCapture;
|
||||
|
||||
/// <summary>
|
||||
/// 捕获的图像
|
||||
/// </summary>
|
||||
public class CaptureImageRes : IDisposable
|
||||
{
|
||||
public Bitmap? Bitmap { get; set; }
|
||||
public Mat? Mat { get; set; }
|
||||
|
||||
public int Width => Mat?.Width ?? Bitmap?.Width ?? 0;
|
||||
public int Height => Mat?.Height ?? Bitmap?.Height ?? 0;
|
||||
|
||||
public CaptureImageRes(Mat mat)
|
||||
{
|
||||
Mat = mat;
|
||||
}
|
||||
|
||||
public CaptureImageRes(Bitmap bitmap)
|
||||
{
|
||||
Bitmap = bitmap;
|
||||
}
|
||||
|
||||
public static CaptureImageRes? BuildNullable(Bitmap? bitmap)
|
||||
{
|
||||
if (bitmap == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new CaptureImageRes(bitmap);
|
||||
}
|
||||
|
||||
public static CaptureImageRes? BuildNullable(Mat? mat)
|
||||
{
|
||||
if (mat == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new CaptureImageRes(mat);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 非特殊情况不要使用这个方法,会造成额外的性能消耗
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Mat? ForceGetMat()
|
||||
{
|
||||
if (Mat == null)
|
||||
{
|
||||
Mat = Bitmap?.ToMat();
|
||||
}
|
||||
return Mat;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 非特殊情况不要使用这个方法,会造成额外的性能消耗
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Bitmap? ForceGetBitmap()
|
||||
{
|
||||
if (Bitmap == null)
|
||||
{
|
||||
Bitmap = Mat?.ToBitmap();
|
||||
}
|
||||
return Bitmap;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Bitmap?.Dispose();
|
||||
Mat?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,18 @@
|
||||
namespace Fischless.GameCapture;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Fischless.GameCapture;
|
||||
|
||||
public enum CaptureModes
|
||||
{
|
||||
[Description("BitBlt(稳定)")]
|
||||
BitBltOld,
|
||||
|
||||
[Description("BitBlt(极速)")]
|
||||
BitBlt,
|
||||
|
||||
[Description("WindowsGraphicsCapture")]
|
||||
WindowsGraphicsCapture,
|
||||
|
||||
[Description("DwmGetDxSharedSurface")]
|
||||
DwmGetDxSharedSurface
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace Fischless.GameCapture.DwmSharedSurface
|
||||
return region;
|
||||
}
|
||||
|
||||
public Mat? Capture()
|
||||
public CaptureImageRes? Capture()
|
||||
{
|
||||
if (_hWnd == nint.Zero)
|
||||
{
|
||||
@@ -98,7 +98,7 @@ namespace Fischless.GameCapture.DwmSharedSurface
|
||||
}
|
||||
var bgrMat = new Mat();
|
||||
Cv2.CvtColor(mat, bgrMat, ColorConversionCodes.BGRA2BGR);
|
||||
return bgrMat;
|
||||
return CaptureImageRes.BuildNullable(bgrMat);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ public class GameCaptureFactory
|
||||
return mode switch
|
||||
{
|
||||
CaptureModes.BitBlt => new BitBlt.BitBltCapture(),
|
||||
CaptureModes.BitBltOld => new BitBlt.BitBltOldCapture(),
|
||||
CaptureModes.WindowsGraphicsCapture => new Graphics.GraphicsCapture(),
|
||||
CaptureModes.DwmGetDxSharedSurface => new DwmSharedSurface.SharedSurfaceCapture(),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
|
||||
|
||||
@@ -278,7 +278,7 @@ public class GraphicsCapture : IGameCapture
|
||||
return sdkMat;
|
||||
}
|
||||
|
||||
public Mat? Capture()
|
||||
public CaptureImageRes? Capture()
|
||||
{
|
||||
// 使用读锁获取最新帧
|
||||
_frameAccessLock.EnterReadLock();
|
||||
@@ -291,7 +291,7 @@ public class GraphicsCapture : IGameCapture
|
||||
}
|
||||
|
||||
// 返回最新帧的副本(这里我们必须克隆,因为Mat是不线程安全的)
|
||||
return _latestFrame.Clone();
|
||||
return CaptureImageRes.BuildNullable(_latestFrame.Clone());
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ public interface IGameCapture : IDisposable
|
||||
|
||||
public void Start(nint hWnd, Dictionary<string, object>? settings = null);
|
||||
|
||||
public Mat? Capture();
|
||||
public CaptureImageRes? Capture();
|
||||
|
||||
public void Stop();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user