Windows GDI 窗口与 Direct3D 屏幕截图( 二 )

安装 nuget 包 SharpDX.Direct3D11 , 简单封装 。此处使用 D3D 11 接口封装 , 对多显卡多显示器的情况只能截取主显卡主显示器画面 , 如需截取其他屏幕 , 需稍微改造构造函数 。截屏可能失败 , 也可能截取到黑屏 , 已经在返回值中提示 。
将 DX 截屏转换成 C# 图像使用了指针操作 , 一方面可以提升性能 , 一方面也是因为都用 DX 了 , 基本上是很难避免底层操作了 , 那就一不做二不休 , 多利用一下 。
1public class DirectXScreenCapturer : IDisposable2{3private Factory1 factory;4private Adapter1 adapter;5private SharpDX.Direct3D11.Device device;6private Output output;7private Output1 output1;8private Texture2DDescription textureDesc;9//2D 纹理 , 存储截屏数据 10private Texture2D screenTexture; 1112public DirectXScreenCapturer() 13{ 14// 获取输出设备(显卡、显示器) , 这里是主显卡和主显示器 15factory = new Factory1(); 16adapter = factory.GetAdapter1(0); 17device = new SharpDX.Direct3D11.Device(adapter); 18output = adapter.GetOutput(0); 19output1 = output.QueryInterface<Output1>(); 2021//设置纹理信息 , 供后续使用(截图大小和质量) 22textureDesc = new Texture2DDescription 23{ 24CpuAccessFlags = CpuAccessFlags.Read, 25BindFlags = BindFlags.None, 26Format = Format.B8G8R8A8_UNorm, 27Width = output.Description.DesktopBounds.Right, 28Height = output.Description.DesktopBounds.Bottom, 29OptionFlags = ResourceOptionFlags.None, 30MipLevels = 1, 31ArraySize = 1, 32SampleDescription = { Count = 1, Quality = 0 }, 33Usage = ResourceUsage.Staging 34}; 3536screenTexture = new Texture2D(device, textureDesc); 37} 3839public Result ProcessFrame(Action<DataBox, Texture2DDescription> processAction, int timeoutInMilliseconds = 5) 40{ 41//截屏 , 可能失败 42using OutputDuplication duplicatedOutput = output1.DuplicateOutput(device); 43var result = duplicatedOutput.TryAcquireNextFrame(timeoutInMilliseconds, out OutputDuplicateFrameInformation duplicateFrameInformation, out SharpDX.DXGI.Resource screenResource); 4445if (!result.Success) return result; 4647using Texture2D screenTexture2D = screenResource.QueryInterface<Texture2D>(); 4849//复制数据 50device.ImmediateContext.CopyResource(screenTexture2D, screenTexture); 51DataBox mapSource = device.ImmediateContext.MapSubresource(screenTexture, 0, MapMode.Read, SharpDX.Direct3D11.MapFlags.None); 5253processAction?.Invoke(mapSource, textureDesc); 5455//释放资源 56device.ImmediateContext.UnmapSubresource(screenTexture, 0); 57screenResource.Dispose(); 58duplicatedOutput.ReleaseFrame(); 5960return result; 61} 6263public (Result result, bool isBlackFrame, Image image) GetFrameImage(int timeoutInMilliseconds = 5) 64{ 65//生成 C# 用图像 66Bitmap image = new Bitmap(textureDesc.Width, textureDesc.Height, PixelFormat.Format24bppRgb); 67bool isBlack = true; 68var result = ProcessFrame(ProcessImage); 6970if (!result.Success) image.Dispose(); 7172return (result, isBlack, result.Success ? image : null); 7374void ProcessImage(DataBox dataBox, Texture2DDescription texture) 75{ 76BitmapData data = https://www.isolves.com/it/rj/czxt/windows/2022-06-06/image.LockBits(new Rectangle(0, 0, texture.Width, texture.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); 7778unsafe 79{ 80byte* dataHead = (byte*)dataBox.DataPointer.ToPointer(); 8182for (int x = 0; x < texture.Width; x++) 83{ 84for (int y = 0; y < texture.Height; y++) 85{ 86byte* pixPtr = (byte*)(data.Scan0 + y * data.Stride + x * 3); 8788int pos = x + y * texture.Width; 89pos *= 4; 9091byte r = dataHead[pos + 2]; 92byte g = dataHead[pos + 1]; 93byte b = dataHead[pos + 0]; 9495if (isBlack && (r != 0 || g != 0 || b != 0)) isBlack = false; 9697pixPtr[0] = b; 98pixPtr[1] = g; 99pixPtr[2] = r;100}101}102}103 104image.UnlockBits(data);105}106}107 108#region IDisposable Support109private bool disposedValue = false; // 要检测冗余调用110 111protected virtual void Dispose(bool disposing)112{113if (!disposedValue)114{115if (disposing)116{117// TODO: 释放托管状态(托管对象) 。118factory.Dispose();119adapter.Dispose();120device.Dispose();121output.Dispose();122output1.Dispose();123screenTexture.Dispose();124}125 126// TODO: 释放未托管的资源(未托管的对象)并在以下内容中替代终结器 。127// TODO: 将大型字段设置为 null 。128factory = null;129adapter = null;130device = null;131output = null;132output1 = null;133screenTexture = null;134 135disposedValue = true;136}137}138 139// TODO: 仅当以上 Dispose(bool disposing) 拥有用于释放未托管资源的代码时才替代终结器 。140// ~DirectXScreenCapturer()141// {142//// 请勿更改此代码 。将清理代码放入以上 Dispose(bool disposing) 中 。143//Dispose(false);144// }145 146// 添加此代码以正确实现可处置模式 。147public void Dispose()148{149// 请勿更改此代码 。将清理代码放入以上 Dispose(bool disposing) 中 。150Dispose(true);151// TODO: 如果在以上内容中替代了终结器 , 则取消注释以下行 。152// GC.SuppressFinalize(this);153}154#endregion155}


推荐阅读