Files
better-genshin-impact/Fischless.GameCapture/BitBlt/BitBltCapture.cs
2025-04-11 00:11:42 +08:00

225 lines
6.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Diagnostics;
using OpenCvSharp;
using Vanara.PInvoke;
using static System.Console;
namespace Fischless.GameCapture.BitBlt;
public class BitBltCapture : IGameCapture
{
public CaptureModes Mode => CaptureModes.BitBlt;
public bool IsCapturing { get; private set; }
private readonly Stopwatch _sizeCheckTimer = new();
private readonly ReaderWriterLockSlim _lockSlim = new();
private volatile nint _hWnd; // 需要加锁
private BitBltSession? _session; // 需要加锁
private volatile bool _lastCaptureFailed;
public void Dispose() => Stop();
public CaptureImageRes? Capture() => Capture(false);
public void Start(nint hWnd, Dictionary<string, object>? settings = null)
{
if (settings == null || !settings.TryGetValue("autoFixWin11BitBlt", out var value)) return;
if (value is true)
{
BitBltRegistryHelper.SetDirectXUserGlobalSettings();
}
_lockSlim.EnterWriteLock();
try
{
_hWnd = hWnd;
if (_hWnd == IntPtr.Zero)
{
return;
}
_session?.Dispose();
_session = null;
IsCapturing = true;
}
finally
{
_lockSlim.ExitWriteLock();
}
CheckSession();
}
/// <summary>
/// 检查窗口大小,如果改变则更新截图尺寸。返回是否成功。
/// </summary>
/// <returns></returns>
private void CheckSession()
{
if (_lockSlim.WaitingWriteCount > 0 || !_lockSlim.TryEnterWriteLock(TimeSpan.FromSeconds(0.5)))
{
// 写锁持有只会有两种情况:start和CheckSession。无论哪一种都会检查&更新session。
// 所以当有线程等待更新时,将直接返回
return;
}
try
{
// 窗口状态变化可能会导致会话失效
// 上次截图失败则重置会话,避免一直截图失败
if (_session is not null && (_session.IsInvalid() || _lastCaptureFailed))
{
_session.Dispose();
_session = null;
}
if (!User32.GetClientRect(_hWnd, out var windowRect) || windowRect == default)
{
// Debug.Fail("Failed to get client rectangle");
// 窗口获取不到或者最小化
_session?.Dispose();
_session = null;
return;
}
var width = windowRect.right - windowRect.left;
var height = windowRect.bottom - windowRect.top;
_session ??= new BitBltSession(_hWnd, width, height);
if (_session.Width == width && _session.Height == height)
{
// 窗口大小没有改变
return;
}
// 窗口尺寸被改变,释放资源,然后重新创建会话
_session.Dispose();
_session = new BitBltSession(_hWnd, width, height);
}
catch (Exception e)
{
Error.WriteLine("[BitBlt]Failed to create session:{0}", e);
}
finally
{
_lockSlim.ExitWriteLock();
}
}
/// <summary>
/// 递归只尝试一次,会设置标志,正常调用置假即可
/// </summary>
/// <param name="recursive">递归标志</param>
/// <returns>截图</returns>
private CaptureImageRes? Capture(bool recursive)
{
if (_hWnd == IntPtr.Zero)
{
return null;
}
if (!_sizeCheckTimer.IsRunning)
{
_sizeCheckTimer.Start();
}
// 不会经常调整窗口尺寸的,所以隔一段时间检查一次就行
// 上次如果截图失败的话忽略计时器,避免重复截图失败
// 递归标志也说明上次截图失败
if (_lastCaptureFailed || recursive || _sizeCheckTimer.ElapsedMilliseconds > 1000)
{
_sizeCheckTimer.Reset();
CheckSession();
}
try
{
_lockSlim.EnterReadLock();
var result = Capture0();
if (result is not null && !result.Empty())
{
// 成功截图
_lastCaptureFailed = false;
return CaptureImageRes.BuildNullable(result);
}
else if (result is null)
{
if (_lastCaptureFailed) return CaptureImageRes.BuildNullable(result); // 这不是首次失败,不再进行尝试
_lastCaptureFailed = true; // 设置失败标志
if (recursive) return CaptureImageRes.BuildNullable(result); // 已设置递归标志,说明也不是首次失败
}
}
finally
{
if (_lockSlim.IsReadLockHeld)
{
_lockSlim.ExitReadLock();
}
}
// 首次出现截图异常会跳到这里
// 首次出现错误重试截图,尽可能不出现截图失败(递归)
return Capture(true);
}
/// <summary>
/// 截图功能的实现。需要加锁后调用,一般只由 Capture 方法调用。
/// </summary>
/// <returns></returns>
private Mat? Capture0()
{
Mat? mat = null;
try
{
if (_session is null)
{
// 没有成功创建会话,直接返回空
return null;
}
mat = _session.GetMat();
if (mat is null) return null; // 执行失败
if (!mat.Empty()) // 成功执行并且获取到了图
{
return mat;
}
else // 成功执行但是没有图,可能是截图过快导致的
{
mat.Dispose();
mat = null; // 防止二次释放
}
return null;
}
catch (Exception e)
{
// 理论这里不应出现异常除非窗口不存在了或者有什么bug
// 出现异常的时候释放内存
mat?.Dispose();
Error.WriteLine("[BitBlt]Failed to capture image {0}", e);
return null;
}
}
public void Stop()
{
_lockSlim.EnterWriteLock();
try
{
_hWnd = IntPtr.Zero;
_sizeCheckTimer.Stop();
if (_session != null)
{
_session.Dispose();
_session = null;
}
}
finally
{
_lockSlim.ExitWriteLock();
}
IsCapturing = false;
}
}