wpf 如何使用WinRT截图一个活动的非焦点窗口

t40tm48m  于 2023-03-24  发布在  其他
关注(0)|答案(1)|浏览(121)

我正在寻找一种方法来截图一个活动(非重点)窗口使用其IntPtr,而不使用user32.dllPrintWindow,因为它需要运行的应用程序作为管理员。
我能够使用WinRT进行屏幕捕获,遵循此repo,并进行了一些调整,使其在dotnet 6 -https://github.com/microsoft/Windows.UI.Composition-Win32-Samples/tree/master/dotnet/WPF/ScreenCapture中工作
现在剩下的就是将帧(Direct3D11CaptureFrame)转换为我可以实际保存的图像。
这是UWP,但其想法是通过执行以下操作将D3 D11曲面转换为Win 2D对象-

// 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);
            }
        }
    ```
u0sqgete

u0sqgete1#

你不需要Direct2D(Win2D是Direct2D的一个 Package )来从一个表面获取字节。你可以使用标准的DirectX API获取字节,然后使用你想要的Bitmap API将它们写入一个文件。
这里是一个使用WinRT的SoftwareBitmapBitmapEncoder的例子,但是你可以使用GDI+(系统.绘图.位图,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
    }

    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();
                        }
                    });
                }
            }
        }
    }
}

相关问题