我正在寻找一种使用其
IntPtr
截取活动(非焦点)窗口的方法,而不使用 user32.dll
PrintWindow
因为它需要以管理员身份运行应用程序。
我能够在这个 repo 之后使用 WinRT 进行屏幕捕获,并进行一些调整以使其在 dotnet 6 中工作 - https://github.com/microsoft/Windows.UI.Composition-Win32-Samples/tree/master/dotnet/WPF/ScreenCapture
现在剩下的就是将帧(
Direct3D11CaptureFrame
)转换为我可以实际保存的图像。
为此,我找到了本教程 -
https://learn.microsoft.com/en-us/windows/uwp/audio-video-camera/screen-capture
它适用于 UWP,但想法是通过执行 -
将 D3D11 表面转换为 Win2D 对象// Convert our D3D11 surface into a Win2D object.
CanvasBitmap canvasBitmap = CanvasBitmap.CreateFromDirect3D11Surface(
_canvasDevice,
frame.Surface);
问题是该示例中的
_canvasDevice
是一个ICanvasResourceCreator
,按照上面共享的WPF屏幕捕获存储库我没有。
我试过使用 - 创建一个设备
CanvasDevice.GetSharedDevice()
但是在
CreateFromDirect3D11Surface
方法中使用该设备会引发异常 -
The requested operation is not supported. (0x88990003)
如果有人知道如何解决该问题或使用不同的方法实现我的目标,我将不胜感激。
谢谢!
编辑- 在整个
OnFrameArrived
方法下方添加,我尝试将框架转换为 CanvasBitmap
就像它在我上面链接的 UWP 代码示例中所做的那样 -
private void OnFrameArrived(Direct3D11CaptureFramePool sender, object args)
{
var newSize = false;
using (var frame = sender.TryGetNextFrame())
{
var canvasDevice = CanvasDevice.GetSharedDevice();
//Convert our D3D11 surface into a Win2D object.
//this method below throws the exception
_currentFrame = CanvasBitmap.CreateFromDirect3D11Surface(
canvasDevice,
frame.Surface);
if (frame.ContentSize.Width != lastSize.Width ||
frame.ContentSize.Height != lastSize.Height)
{
// The thing we have been capturing has changed size.
// We need to resize the swap chain first, then blit the pixels.
// After we do that, retire the frame and then recreate the frame pool.
newSize = true;
lastSize = frame.ContentSize;
swapChain.ResizeBuffers(
2,
lastSize.Width,
lastSize.Height,
Format.B8G8R8A8_UNorm,
SwapChainFlags.None);
}
using (var backBuffer = swapChain.GetBackBuffer<Texture2D>(0))
using (var bitmap = Direct3D11Helper.CreateSharpDXTexture2D(frame.Surface))
{
d3dDevice.ImmediateContext.CopyResource(bitmap, backBuffer);
}
} // Retire the frame.
swapChain.Present(0, PresentFlags.None);
if (newSize)
{
framePool.Recreate(
device,
DirectXPixelFormat.B8G8R8A8UIntNormalized,
2,
lastSize);
}
}
```
您不需要 Direct2D(Win2D 是 Direct2D 的包装器)来从表面获取字节。您可以使用标准 DirectX API 获取字节,然后使用所需的位图 API 将它们写入文件。
这里是一个使用WinRT的SoftwareBitmap和BitmapEncoder的例子,但是你可以使用GDI+(System.Drawing.Bitmap, WIC等)来保存一个文件。
从BasicCapture.cs文件开始,修改如下:
public class BasicCapture : IDisposable
{
...
private Texture2D cpuTexture; // add this
private bool saved = false; // add this
public BasicCapture(IDirect3DDevice d, GraphicsCaptureItem i)
{
...
cpuTexture = CreateTexture2D(item.Size.Width, item.Size.Height); // add this
}
public void Dispose()
{
session?.Dispose();
framePool?.Dispose();
swapChain?.Dispose();
d3dDevice?.Dispose();
cpuTexture?.Dispose(); // add this
}
private Texture2D CreateTexture2D(int width, int height)
{
// create add texture2D 2D accessible by the CPU
var desc = new Texture2DDescription()
{
Width = width,
Height = height,
CpuAccessFlags = CpuAccessFlags.Read,
Usage = ResourceUsage.Staging,
Format = Format.B8G8R8A8_UNorm,
ArraySize = 1,
MipLevels = 1,
SampleDescription = new SampleDescription(1, 0),
};
return new Texture2D(d3dDevice, desc);
}
private void OnFrameArrived(Direct3D11CaptureFramePool sender, object args)
{
using (var frame = sender.TryGetNextFrame())
{
...
using (var backBuffer = swapChain.GetBackBuffer<SharpDX.Direct3D11.Texture2D>(0))
using (var bitmap = Direct3D11Helper.CreateSharpDXTexture2D(frame.Surface))
{
d3dDevice.ImmediateContext.CopyResource(bitmap, backBuffer);
// add this to copy the DirectX resource into the CPU-readable texture2D
d3dDevice.ImmediateContext.CopyResource(bitmap, cpuTexture);
// now, this is just an example that only saves the first frame
// but you could also use
// d3dDevice.ImmediateContext.MapSubresource(cpuTexture, 0, MapMode.Read, SharpDX.Direct3D11.MapFlags.None, out var stream); and d3dDevice.ImmediateContext.UnMapSubresource
// to get the bytes (out from the returned stream)
if (!_saved)
{
_saved = true;
Task.Run(async () =>
{
// get IDirect3DSurface from texture (from WPF sample's helper code)
var surf = Direct3D11Helper.CreateDirect3DSurfaceFromSharpDXTexture(cpuTexture);
// build a WinRT's SoftwareBitmap from this surface/texture
var softwareBitmap = await SoftwareBitmap.CreateCopyFromSurfaceAsync(surf);
using (var file = new FileStream(@"c:\temp\test.png", FileMode.Create, FileAccess.Write))
{
// create a PNG encoder
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, file.AsRandomAccessStream());
// set the bitmap to it & flush
encoder.SetSoftwareBitmap(softwareBitmap);
await encoder.FlushAsync();
}
});
}
}
}
}
}