diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Media/Rgba32.cs b/src/Snap.Hutao/Snap.Hutao/Control/Media/Rgba32.cs
index a8c70b55..33aacc36 100644
--- a/src/Snap.Hutao/Snap.Hutao/Control/Media/Rgba32.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Media/Rgba32.cs
@@ -8,45 +8,19 @@ using Windows.UI;
namespace Snap.Hutao.Control.Media;
-///
-/// RGBA 颜色
-///
[HighQuality]
internal struct Rgba32
{
- ///
- /// R
- ///
public byte R;
-
- ///
- /// G
- ///
public byte G;
-
- ///
- /// B
- ///
public byte B;
-
- ///
- /// A
- ///
public byte A;
- ///
- /// 构造一个新的 RGBA8 颜色
- ///
- /// 色值字符串
public Rgba32(string hex)
: this(hex.Length == 6 ? Convert.ToUInt32($"{hex}FF", 16) : Convert.ToUInt32(hex, 16))
{
}
- ///
- /// 使用 RGBA 代码初始化新的结构
- ///
- /// RGBA 代码
public unsafe Rgba32(uint xrgbaCode)
{
// uint layout: 0xRRGGBBAA is AABBGGRR
@@ -80,11 +54,6 @@ internal struct Rgba32
return *(Color*)&rgba;
}
- ///
- /// 从 HSL 颜色转换
- ///
- /// HSL 颜色
- /// RGBA8颜色
public static Rgba32 FromHsl(Hsla32 hsl)
{
double chroma = (1 - Math.Abs((2 * hsl.L) - 1)) * hsl.S;
@@ -138,10 +107,6 @@ internal struct Rgba32
return new(r, g, b, a);
}
- ///
- /// 转换到 HSL 颜色
- ///
- /// HSL 颜色
public readonly Hsla32 ToHsl()
{
const double toDouble = 1.0 / 255;
diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Media/Rgba64.cs b/src/Snap.Hutao/Snap.Hutao/Control/Media/Rgba64.cs
new file mode 100644
index 00000000..ba1d15b6
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Control/Media/Rgba64.cs
@@ -0,0 +1,14 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+// Some part of this file came from:
+// https://github.com/xunkong/desktop/tree/main/src/Desktop/Desktop/Pages/CharacterInfoPage.xaml.cs
+
+namespace Snap.Hutao.Control.Media;
+
+internal struct Rgba64
+{
+ public Half R;
+ public Half G;
+ public Half B;
+ public Half A;
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Properties/launchSettings.json b/src/Snap.Hutao/Snap.Hutao/Properties/launchSettings.json
index 36a6b395..a5fb2a14 100644
--- a/src/Snap.Hutao/Snap.Hutao/Properties/launchSettings.json
+++ b/src/Snap.Hutao/Snap.Hutao/Properties/launchSettings.json
@@ -2,7 +2,7 @@
"profiles": {
"Snap.Hutao": {
"commandName": "MsixPackage",
- "nativeDebugging": true,
+ "nativeDebugging": false,
"doNotLaunchApp": false,
"allowLocalNetworkLoopbackProperty": true
},
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Automation/ScreenCapture/GameScreenCaptureContext.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Automation/ScreenCapture/GameScreenCaptureContext.cs
index d012a2f9..ccb0d299 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Automation/ScreenCapture/GameScreenCaptureContext.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Automation/ScreenCapture/GameScreenCaptureContext.cs
@@ -52,19 +52,19 @@ internal readonly struct GameScreenCaptureContext
{
clientBox = default;
- // Check if the window is minimized
+ // Ensure the window is not minimized
if (IsIconic(hwnd))
{
return false;
}
- // Check if the window is at least partially in the screen
+ // Ensure the window is at least partially in the screen
if (!(GetClientRect(hwnd, out RECT clientRect) && (clientRect.right > 0) && (clientRect.bottom > 0)))
{
return false;
}
- // Make sure we get the window chrome rect
+ // Ensure we get the window chrome rect
if (DwmGetWindowAttribute(hwnd, DWMWINDOWATTRIBUTE.DWMWA_EXTENDED_FRAME_BOUNDS, out RECT windowRect) != HRESULT.S_OK)
{
return false;
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Automation/ScreenCapture/GameScreenCaptureResult.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Automation/ScreenCapture/GameScreenCaptureResult.cs
new file mode 100644
index 00000000..8aa4ceb3
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Automation/ScreenCapture/GameScreenCaptureResult.cs
@@ -0,0 +1,29 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Buffers;
+
+namespace Snap.Hutao.Service.Game.Automation.ScreenCapture;
+
+internal sealed class GameScreenCaptureResult : IDisposable
+{
+ private readonly IMemoryOwner rawPixelData;
+ private readonly int pixelWidth;
+ private readonly int pixelHeight;
+
+ public GameScreenCaptureResult(IMemoryOwner rawPixelData, int pixelWidth, int pixelHeight)
+ {
+ this.rawPixelData = rawPixelData;
+ this.pixelWidth = pixelWidth;
+ this.pixelHeight = pixelHeight;
+ }
+
+ public int PixelWidth { get => pixelWidth; }
+
+ public int PixelHeight { get => pixelHeight; }
+
+ public void Dispose()
+ {
+ rawPixelData.Dispose();
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Automation/ScreenCapture/GameScreenCaptureSession.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Automation/ScreenCapture/GameScreenCaptureSession.cs
index 4a3d3bc9..af8f54a5 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Automation/ScreenCapture/GameScreenCaptureSession.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Automation/ScreenCapture/GameScreenCaptureSession.cs
@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
+using Snap.Hutao.Control.Media;
using Snap.Hutao.Core;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Win32.Graphics.Direct3D11;
@@ -8,7 +9,9 @@ using Snap.Hutao.Win32.Graphics.Dxgi;
using Snap.Hutao.Win32.Graphics.Dxgi.Common;
using Snap.Hutao.Win32.System.WinRT.Graphics.Capture;
using System.Buffers;
+using System.Numerics;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using Windows.Graphics;
using Windows.Graphics.Capture;
using Windows.Graphics.DirectX.Direct3D11;
@@ -19,12 +22,14 @@ namespace Snap.Hutao.Service.Game.Automation.ScreenCapture;
internal sealed class GameScreenCaptureSession : IDisposable
{
+ private static readonly Half ByteMaxValue = 255;
+
private readonly GameScreenCaptureContext captureContext;
private readonly Direct3D11CaptureFramePool framePool;
private readonly GraphicsCaptureSession session;
private readonly ILogger logger;
- private TaskCompletionSource>? frameRawPixelDataTaskCompletionSource;
+ private TaskCompletionSource? frameRawPixelDataTaskCompletionSource;
private bool isFrameRawPixelDataRequested;
private SizeInt32 contentSize;
@@ -47,7 +52,7 @@ internal sealed class GameScreenCaptureSession : IDisposable
session.StartCapture();
}
- public async ValueTask> RequestFrameRawPixelDataAsync()
+ public async ValueTask RequestFrameAsync()
{
if (Volatile.Read(ref isFrameRawPixelDataRequested))
{
@@ -170,7 +175,7 @@ internal sealed class GameScreenCaptureSession : IDisposable
if (boxAvailable)
{
-
+ pD3D11DeviceContext->CopySubresourceRegion((ID3D11Resource*)pD3D11Texture2D, 0U, 0U, 0U, 0U, pD3D11Resource, 0U, in clientBox);
}
else
{
@@ -190,17 +195,53 @@ internal sealed class GameScreenCaptureSession : IDisposable
// │ Actual data │ Stride │
// │ │ │
// └────────────────────┴─────────┘
- ReadOnlySpan2D subresource = new(d3d11MappedSubresource.pData, (int)d3d11Texture2DDesc.Height, (int)d3d11MappedSubresource.RowPitch);
-
- int rowLength = contentSize.Width * 4;
- IMemoryOwner buffer = GameScreenCaptureMemoryPool.Shared.Rent(contentSize.Height * rowLength);
-
- for (int row = 0; row < contentSize.Height; row++)
- {
- subresource[row][..rowLength].CopyTo(buffer.Memory.Span.Slice(row * rowLength, rowLength));
- }
+ ReadOnlySpan2D subresource = new(d3d11MappedSubresource.pData, (int)textureHeight, (int)d3d11MappedSubresource.RowPitch);
ArgumentNullException.ThrowIfNull(frameRawPixelDataTaskCompletionSource);
- frameRawPixelDataTaskCompletionSource.SetResult(buffer);
+ switch (dxgiSurfaceDesc.Format)
+ {
+ case DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM:
+ {
+ int rowLength = (int)textureWidth * 4;
+ IMemoryOwner buffer = GameScreenCaptureMemoryPool.Shared.Rent((int)(textureHeight * textureWidth * 4));
+
+ for (int row = 0; row < textureHeight; row++)
+ {
+ subresource[row][..rowLength].CopyTo(buffer.Memory.Span.Slice(row * rowLength, rowLength));
+ }
+
+ frameRawPixelDataTaskCompletionSource.SetResult(new(buffer, (int)textureWidth, (int)textureHeight));
+ return;
+ }
+
+ case DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT:
+ {
+ int rowLength = (int)textureWidth * 8;
+ IMemoryOwner buffer = GameScreenCaptureMemoryPool.Shared.Rent((int)(textureHeight * textureWidth * 4));
+ Span pixelBuffer = MemoryMarshal.Cast(buffer.Memory.Span);
+
+ for (int row = 0; row < textureHeight; row++)
+ {
+ ReadOnlySpan subresourceRow = MemoryMarshal.Cast(subresource[row][..rowLength]);
+ Span bufferRow = pixelBuffer.Slice(row * (int)textureWidth, (int)textureWidth);
+ for (int column = 0; column < textureWidth; column++)
+ {
+ ref readonly Rgba64 float16Pixel = ref subresourceRow[column];
+ ref Bgra32 pixel = ref bufferRow[column];
+ pixel.B = (byte)(float16Pixel.B * ByteMaxValue);
+ pixel.G = (byte)(float16Pixel.G * ByteMaxValue);
+ pixel.R = (byte)(float16Pixel.R * ByteMaxValue);
+ pixel.A = (byte)(float16Pixel.A * ByteMaxValue);
+ }
+ }
+
+ frameRawPixelDataTaskCompletionSource.SetResult(new(buffer, (int)textureWidth, (int)textureHeight));
+ return;
+ }
+
+ default:
+ HutaoException.NotSupported($"Unexpected DXGI_FORMAT: {dxgiSurfaceDesc.Format}");
+ return;
+ }
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs
index d1aeabe9..0ab90962 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs
@@ -14,6 +14,7 @@ using Snap.Hutao.Web.Hutao.HutaoAsAService;
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.Graphics.Direct3D;
using Snap.Hutao.Win32.Graphics.Direct3D11;
+using Snap.Hutao.Win32.Graphics.Dwm;
using Snap.Hutao.Win32.Graphics.Dxgi;
using Snap.Hutao.Win32.Graphics.Dxgi.Common;
using Snap.Hutao.Win32.System.Com;
@@ -30,7 +31,9 @@ using Windows.Storage.Streams;
using WinRT;
using static Snap.Hutao.Win32.ConstValues;
using static Snap.Hutao.Win32.D3D11;
+using static Snap.Hutao.Win32.DwmApi;
using static Snap.Hutao.Win32.Macros;
+using static Snap.Hutao.Win32.User32;
namespace Snap.Hutao.ViewModel;
@@ -214,9 +217,14 @@ internal sealed partial class TestViewModel : Abstraction.ViewModel
return;
}
+ bool boxAvailable = TryGetClientBox(hwnd, surfaceDesc.Width, surfaceDesc.Height, out D3D11_BOX clientBox);
+ (uint textureWidth, uint textureHeight) = boxAvailable
+ ? (clientBox.right - clientBox.left, clientBox.bottom - clientBox.top)
+ : (surfaceDesc.Width, surfaceDesc.Height);
+
D3D11_TEXTURE2D_DESC texture2DDesc = default;
- texture2DDesc.Width = surfaceDesc.Width;
- texture2DDesc.Height = surfaceDesc.Height;
+ texture2DDesc.Width = textureWidth;
+ texture2DDesc.Height = textureHeight;
texture2DDesc.ArraySize = 1;
texture2DDesc.CPUAccessFlags = D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ;
texture2DDesc.Format = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM;
@@ -240,15 +248,22 @@ internal sealed partial class TestViewModel : Abstraction.ViewModel
}
pD3D11Device->GetImmediateContext(out ID3D11DeviceContext* pDeviceContext);
- pDeviceContext->CopyResource((ID3D11Resource*)pTexture2D, pD3D11Resource);
+
+ if (boxAvailable)
+ {
+ pDeviceContext->CopySubresourceRegion((ID3D11Resource*)pTexture2D, 0, 0, 0, 0, pD3D11Resource, 0, in clientBox);
+ }
+ else
+ {
+ logger.LogInformation("Box not available");
+ pDeviceContext->CopyResource((ID3D11Resource*)pTexture2D, pD3D11Resource);
+ }
if (FAILED(pDeviceContext->Map((ID3D11Resource*)pTexture2D, 0, D3D11_MAP.D3D11_MAP_READ, 0, out D3D11_MAPPED_SUBRESOURCE mappedSubresource)))
{
return;
}
- int size = (int)(mappedSubresource.RowPitch * texture2DDesc.Height * 4);
-
SoftwareBitmap softwareBitmap = new(BitmapPixelFormat.Bgra8, (int)texture2DDesc.Width, (int)texture2DDesc.Height, BitmapAlphaMode.Premultiplied);
using (BitmapBuffer bitmapBuffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Write))
{
@@ -314,5 +329,45 @@ internal sealed partial class TestViewModel : Abstraction.ViewModel
{
logger.LogWarning("D3D11CreateDevice failed");
}
+
+ static bool TryGetClientBox(HWND hwnd, uint width, uint height, out D3D11_BOX clientBox)
+ {
+ clientBox = default;
+ return false;
+
+ // Ensure the window is not minimized
+ if (IsIconic(hwnd))
+ {
+ return false;
+ }
+
+ // Ensure the window is at least partially in the screen
+ if (!(GetClientRect(hwnd, out RECT clientRect) && (clientRect.right > 0) && (clientRect.bottom > 0)))
+ {
+ return false;
+ }
+
+ // Ensure we get the window chrome rect
+ if (DwmGetWindowAttribute(hwnd, DWMWINDOWATTRIBUTE.DWMWA_EXTENDED_FRAME_BOUNDS, out RECT windowRect) != HRESULT.S_OK)
+ {
+ return false;
+ }
+
+ // Provide a client side (0, 0) and translate to screen coordinates
+ POINT clientPoint = default;
+ if (!ClientToScreen(hwnd, ref clientPoint))
+ {
+ return false;
+ }
+
+ uint left = clientBox.left = clientPoint.x > windowRect.left ? (uint)(clientPoint.x - windowRect.left) : 0U;
+ uint top = clientBox.top = clientPoint.y > windowRect.top ? (uint)(clientPoint.y - windowRect.top) : 0U;
+ clientBox.right = left + (width > left ? (uint)Math.Min(width - left, clientRect.right) : 1U);
+ clientBox.bottom = top + (height > top ? (uint)Math.Min(height - top, clientRect.bottom) : 1U);
+ clientBox.front = 0U;
+ clientBox.back = 1U;
+
+ return clientBox.right <= width && clientBox.bottom <= height;
+ }
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Win32/Graphics/Direct3D11/ID3D11DeviceContext.cs b/src/Snap.Hutao/Snap.Hutao/Win32/Graphics/Direct3D11/ID3D11DeviceContext.cs
index 2dc71a1d..fd0ebe6e 100644
--- a/src/Snap.Hutao/Snap.Hutao/Win32/Graphics/Direct3D11/ID3D11DeviceContext.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Win32/Graphics/Direct3D11/ID3D11DeviceContext.cs
@@ -39,9 +39,13 @@ internal unsafe struct ID3D11DeviceContext
ThisPtr->Unmap((ID3D11DeviceContext*)Unsafe.AsPointer(ref this), pResource, Subresource);
}
- public void CopySubresourceRegion(ID3D11Resource* pDstResource, uint DstSubresource, uint DstX, uint DstY, uint DstZ, ID3D11Resource* pSrcResource, uint SrcSubresource, D3D11_BOX* pSrcBox)
+ [SuppressMessage("", "SA1313")]
+ public unsafe void CopySubresourceRegion(ID3D11Resource* pDstResource, uint DstSubresource, uint DstX, uint DstY, uint DstZ, ID3D11Resource* pSrcResource, uint SrcSubresource, [AllowNull] ref readonly D3D11_BOX srcBox)
{
- ThisPtr->CopySubresourceRegion((ID3D11DeviceContext*)Unsafe.AsPointer(ref this), pDstResource, DstSubresource, DstX, DstY, DstZ, pSrcResource, SrcSubresource, pSrcBox);
+ fixed (D3D11_BOX* pSrcBox = &srcBox)
+ {
+ ThisPtr->CopySubresourceRegion((ID3D11DeviceContext*)Unsafe.AsPointer(ref this), pDstResource, DstSubresource, DstX, DstY, DstZ, pSrcResource, SrcSubresource, pSrcBox);
+ }
}
public void CopyResource(ID3D11Resource* pDstResource, ID3D11Resource* pSrcResource)