如何用WinRT截取非活跃窗口截图及解决CanvasBitmap创建异常
解决方案:Direct3D11CaptureFrame转可保存图像的异常修复与替代方案
异常原因分析
CanvasDevice.GetSharedDevice()抛出0x88990003异常,是因为该方法创建的设备与WinRT屏幕捕获框架使用的Direct3D设备不兼容,两者属于不同的设备上下文,无法直接共享资源。
方案一:兼容Win2D的设备创建方式
不要使用GetSharedDevice(),而是基于你已有的d3dDevice(SharpDX的Direct3D11.Device)创建与捕获帧池兼容的CanvasDevice:
修改OnFrameArrived方法中创建canvasDevice的代码:
private void OnFrameArrived(Direct3D11CaptureFramePool sender, object args) { var newSize = false; using (var frame = sender.TryGetNextFrame()) { // 替换原有CanvasDevice.GetSharedDevice()的代码 using var winrtD3dDevice = Direct3D11Helper.CreateDirect3DDevice(d3dDevice); var canvasDevice = CanvasDevice.CreateFromDirect3D11Device(winrtD3dDevice); // 现在可以正常转换表面 _currentFrame = CanvasBitmap.CreateFromDirect3D11Surface(canvasDevice, frame.Surface); // 后续原有代码保持不变... if (frame.ContentSize.Width != lastSize.Width || frame.ContentSize.Height != lastSize.Height) { 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); } } swapChain.Present(0, PresentFlags.None); if (newSize) { framePool.Recreate( device, DirectXPixelFormat.B8G8R8A8UIntNormalized, 2, lastSize); } }
通过Direct3D11Helper.CreateDirect3DDevice将SharpDX设备转为WinRT兼容的IDirect3DDevice,再创建对应的CanvasDevice,就能与捕获的frame.Surface资源兼容,避免异常。
方案二:无需Win2D,直接用SharpDX读取像素保存图像
如果不需要Win2D的后续处理,可以直接读取捕获到的Texture2D像素数据,转换成标准图像格式保存:
在CopyResource之后添加以下代码:
using (var backBuffer = swapChain.GetBackBuffer<Texture2D>(0)) using (var bitmap = Direct3D11Helper.CreateSharpDXTexture2D(frame.Surface)) { d3dDevice.ImmediateContext.CopyResource(bitmap, backBuffer); // 读取纹理像素数据 var subresource = d3dDevice.ImmediateContext.MapSubresource(bitmap, 0, MapMode.Read, MapFlags.None); try { // 创建GDI Bitmap(注意像素格式转换:D3D是B8G8R8A8,GDI是ARGB) var outputBitmap = new Bitmap( (int)lastSize.Width, (int)lastSize.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb); var bitmapData = outputBitmap.LockBits( new Rectangle(0, 0, outputBitmap.Width, outputBitmap.Height), ImageLockMode.WriteOnly, outputBitmap.PixelFormat); try { // 逐行复制并转换通道顺序 for (int y = 0; y < outputBitmap.Height; y++) { IntPtr srcRow = subresource.DataPointer + y * subresource.RowPitch; IntPtr destRow = bitmapData.Scan0 + y * bitmapData.Stride; // 每行复制并转换BGR到RGB for (int x = 0; x < outputBitmap.Width; x++) { byte b = Marshal.ReadByte(srcRow, x * 4); byte g = Marshal.ReadByte(srcRow, x * 4 + 1); byte r = Marshal.ReadByte(srcRow, x * 4 + 2); byte a = Marshal.ReadByte(srcRow, x * 4 + 3); Marshal.WriteByte(destRow, x * 4, r); Marshal.WriteByte(destRow, x * 4 + 1, g); Marshal.WriteByte(destRow, x * 4 + 2, b); Marshal.WriteByte(destRow, x * 4 + 3, a); } } // 保存图像 outputBitmap.Save("window_capture.png", ImageFormat.Png); } finally { outputBitmap.UnlockBits(bitmapData); } } finally { d3dDevice.ImmediateContext.UnmapSubresource(bitmap, 0); } }
注意事项
- 需引用
System.Drawing.Common包(.NET 6+需手动安装) - 像素格式转换不可省略,否则图像颜色会出现颠倒
内容的提问来源于stack exchange,提问作者Ben
相关产品推荐
相关产品推荐

