您当前的位置:首页 > 电脑百科 > 软件技术 > 操作系统 > windows

Windows GDI 窗口与 Direct3D 屏幕截图

时间:2022-06-06 15:29:29  来源:  作者:java领域

前言

windows 上,屏幕截图一般是调用 win32 api 完成的,如果 C# 想实现截图功能,就需要封装相关 api。在 Windows 上,主要图形接口有 GDI 和 DirectX。GDI 接口比较灵活,可以截取指定窗口,哪怕窗口被遮挡或位于显示区域外,但兼容性较低,无法截取 DX 接口输出的画面。DirectX 是高性能图形接口(当然还有其他功能,与本文无关,忽略不计),主要作为游戏图形接口使用,灵活性较低,无法指定截取特定窗口(或者只是我不会吧),但是兼容性较高,可以截取任何输出到屏幕的内容,根据情况使用。

正文

以下代码使用了 C# 8.0 的新功能,只能使用 VS 2019 编译,如果需要在老版本 VS 使用,需要自行改造。

GDI

用静态类简单封装 GDI 接口并调用接口截图。

  1     public static class CaptureWindow
  2     {
  3         #region 类
  4         /// <summary>
  5         /// Helper class contAIning User32 API functions
  6         /// </summary>
  7         private class User32
  8         {
  9             [StructLayout(LayoutKind.Sequential)]
 10             public struct RECT
 11             {
 12                 public int left;
 13                 public int top;
 14                 public int right;
 15                 public int bottom;
 16             }
 17             [DllImport("user32.dll")]
 18             public static extern IntPtr GetDesktopWindow();
 19             [DllImport("user32.dll")]
 20             public static extern IntPtr GetWindowDC(IntPtr hWnd);
 21             [DllImport("user32.dll")]
 22             public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC);
 23             [DllImport("user32.dll")]
 24             public static extern IntPtr GetWindowRect(IntPtr hWnd, ref RECT rect);
 25 
 26             [DllImport("user32.dll", EntryPoint = "FindWindow", CharSet = CharSet.Unicode)]
 27             public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
 28         }
 29 
 30         private class Gdi32
 31         {
 32 
 33             public const int SRCCOPY = 0x00CC0020; // BitBlt dwRop parameter
 34             [DllImport("gdi32.dll")]
 35             public static extern bool BitBlt(IntPtr hObject, int nXDest, int nYDest,
 36                 int nWidth, int nHeight, IntPtr hObjectSource,
 37                 int nXSrc, int nYSrc, int dwRop);
 38             [DllImport("gdi32.dll")]
 39             public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int nWidth,
 40                 int nHeight);
 41             [DllImport("gdi32.dll")]
 42             public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
 43             [DllImport("gdi32.dll")]
 44             public static extern bool DeleteDC(IntPtr hDC);
 45             [DllImport("gdi32.dll")]
 46             public static extern bool DeleteObject(IntPtr hObject);
 47             [DllImport("gdi32.dll")]
 48             public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
 49         }
 50         #endregion
 51 
 52         /// <summary>
 53         /// 根据句柄截图
 54         /// </summary>
 55         /// <param name="hWnd">句柄</param>
 56         /// <returns></returns>
 57         public static Image ByHwnd(IntPtr hWnd)
 58         {
 59             // get te hDC of the target window
 60             IntPtr hdcSrc = User32.GetWindowDC(hWnd);
 61             // get the size
 62             User32.RECT windowRect = new User32.RECT();
 63             User32.GetWindowRect(hWnd, ref windowRect);
 64             int width = windowRect.right - windowRect.left;
 65             int height = windowRect.bottom - windowRect.top;
 66             // create a device context we can copy to
 67             IntPtr hdcDest = Gdi32.CreateCompatibleDC(hdcSrc);
 68             // create a bitmap we can copy it to,
 69             // using GetDeviceCaps to get the width/height
 70             IntPtr hBitmap = Gdi32.CreateCompatibleBitmap(hdcSrc, width, height);
 71             // select the bitmap object
 72             IntPtr hOld = Gdi32.SelectObject(hdcDest, hBitmap);
 73             // bitblt over
 74             Gdi32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, Gdi32.SRCCOPY);
 75             // restore selection
 76             Gdi32.SelectObject(hdcDest, hOld);
 77             // clean up
 78             Gdi32.DeleteDC(hdcDest);
 79             User32.ReleaseDC(hWnd, hdcSrc);
 80             // get a .NET image object for it
 81             Image img = Image.FromHbitmap(hBitmap);
 82             // free up the Bitmap object
 83             Gdi32.DeleteObject(hBitmap);
 84             return img;
 85         }
 86 
 87         /// <summary>
 88         /// 根据窗口名称截图
 89         /// </summary>
 90         /// <param name="windowName">窗口名称</param>
 91         /// <returns></returns>
 92         public static Image ByName(string windowName)
 93         {
 94             IntPtr handle = User32.FindWindow(null, windowName);
 95             IntPtr hdcSrc = User32.GetWindowDC(handle);
 96             User32.RECT windowRect = new User32.RECT();
 97             User32.GetWindowRect(handle, ref windowRect);
 98             int width = windowRect.right - windowRect.left;
 99             int height = windowRect.bottom - windowRect.top;
100             IntPtr hdcDest = Gdi32.CreateCompatibleDC(hdcSrc);
101             IntPtr hBitmap = Gdi32.CreateCompatibleBitmap(hdcSrc, width, height);
102             IntPtr hOld = Gdi32.SelectObject(hdcDest, hBitmap);
103             Gdi32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, Gdi32.SRCCOPY);
104             Gdi32.SelectObject(hdcDest, hOld);
105             Gdi32.DeleteDC(hdcDest);
106             User32.ReleaseDC(handle, hdcSrc);
107             Image img = Image.FromHbitmap(hBitmap);
108             Gdi32.DeleteObject(hBitmap);
109             return img;
110         }
111     }

Direct3D

安装 nuget 包 SharpDX.Direct3D11,简单封装。此处使用 D3D 11 接口封装,对多显卡多显示器的情况只能截取主显卡主显示器画面,如需截取其他屏幕,需稍微改造构造函数。截屏可能失败,也可能截取到黑屏,已经在返回值中提示。

将 DX 截屏转换成 C# 图像使用了指针操作,一方面可以提升性能,一方面也是因为都用 DX 了,基本上是很难避免底层操作了,那就一不做二不休,多利用一下。

  1     public class DirectXScreenCapturer : IDisposable
  2     {
  3         private Factory1 factory;
  4         private Adapter1 adapter;
  5         private SharpDX.Direct3D11.Device device;
  6         private Output output;
  7         private Output1 output1;
  8         private Texture2DDescription textureDesc;
  9         //2D 纹理,存储截屏数据
 10         private Texture2D screenTexture;
 11 
 12         public DirectXScreenCapturer()
 13         {
 14             // 获取输出设备(显卡、显示器),这里是主显卡和主显示器
 15             factory = new Factory1();
 16             adapter = factory.GetAdapter1(0);
 17             device = new SharpDX.Direct3D11.Device(adapter);
 18             output = adapter.GetOutput(0);
 19             output1 = output.QueryInterface<Output1>();
 20 
 21             //设置纹理信息,供后续使用(截图大小和质量)
 22             textureDesc = new Texture2DDescription
 23             {
 24                 CpuAccessFlags = CpuAccessFlags.Read,
 25                 BindFlags = BindFlags.None,
 26                 Format = Format.B8G8R8A8_UNorm,
 27                 Width = output.Description.DesktopBounds.Right,
 28                 Height = output.Description.DesktopBounds.Bottom,
 29                 OptionFlags = ResourceOptionFlags.None,
 30                 MipLevels = 1,
 31                 ArraySize = 1,
 32                 SampleDescription = { Count = 1, Quality = 0 },
 33                 Usage = ResourceUsage.Staging
 34             };
 35 
 36             screenTexture = new Texture2D(device, textureDesc);
 37         }
 38 
 39         public Result ProcessFrame(Action<DataBox, Texture2DDescription> processAction, int timeoutInMilliseconds = 5)
 40         {
 41             //截屏,可能失败
 42             using OutputDuplication duplicatedOutput = output1.DuplicateOutput(device);
 43             var result = duplicatedOutput.TryAcquireNextFrame(timeoutInMilliseconds, out OutputDuplicateFrameInformation duplicateFrameInformation, out SharpDX.DXGI.Resource screenResource);
 44 
 45             if (!result.Success) return result;
 46 
 47             using Texture2D screenTexture2D = screenResource.QueryInterface<Texture2D>();
 48 
 49             //复制数据
 50             device.ImmediateContext.CopyResource(screenTexture2D, screenTexture);
 51             DataBox mapSource = device.ImmediateContext.MapSubresource(screenTexture, 0, MapMode.Read, SharpDX.Direct3D11.MapFlags.None);
 52 
 53             processAction?.Invoke(mapSource, textureDesc);
 54 
 55             //释放资源
 56             device.ImmediateContext.UnmapSubresource(screenTexture, 0);
 57             screenResource.Dispose();
 58             duplicatedOutput.ReleaseFrame();
 59 
 60             return result;
 61         }
 62 
 63         public (Result result, bool isBlackFrame, Image image) GetFrameImage(int timeoutInMilliseconds = 5)
 64         {
 65             //生成 C# 用图像
 66             Bitmap image = new Bitmap(textureDesc.Width, textureDesc.Height, PixelFormat.Format24bppRgb);
 67             bool isBlack = true;
 68             var result = ProcessFrame(ProcessImage);
 69 
 70             if (!result.Success) image.Dispose();
 71 
 72             return (result, isBlack, result.Success ? image : null);
 73 
 74             void ProcessImage(DataBox dataBox, Texture2DDescription texture)
 75             {
 76                 BitmapData data = image.LockBits(new Rectangle(0, 0, texture.Width, texture.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
 77 
 78                 unsafe
 79                 {
 80                     byte* dataHead = (byte*)dataBox.DataPointer.ToPointer();
 81 
 82                     for (int x = 0; x < texture.Width; x++)
 83                     {
 84                         for (int y = 0; y < texture.Height; y++)
 85                         {
 86                             byte* pixPtr = (byte*)(data.Scan0 + y * data.Stride + x * 3);
 87 
 88                             int pos = x + y * texture.Width;
 89                             pos *= 4;
 90 
 91                             byte r = dataHead[pos + 2];
 92                             byte g = dataHead[pos + 1];
 93                             byte b = dataHead[pos + 0];
 94 
 95                             if (isBlack && (r != 0 || g != 0 || b != 0)) isBlack = false;
 96 
 97                             pixPtr[0] = b;
 98                             pixPtr[1] = g;
 99                             pixPtr[2] = r;
100                         }
101                     }
102                 }
103 
104                 image.UnlockBits(data);
105             }
106         }
107 
108         #region IDisposable Support
109         private bool disposedValue = false; // 要检测冗余调用
110 
111         protected virtual void Dispose(bool disposing)
112         {
113             if (!disposedValue)
114             {
115                 if (disposing)
116                 {
117                     // TODO: 释放托管状态(托管对象)。
118                     factory.Dispose();
119                     adapter.Dispose();
120                     device.Dispose();
121                     output.Dispose();
122                     output1.Dispose();
123                     screenTexture.Dispose();
124                 }
125 
126                 // TODO: 释放未托管的资源(未托管的对象)并在以下内容中替代终结器。
127                 // TODO: 将大型字段设置为 null。
128                 factory = null;
129                 adapter = null;
130                 device = null;
131                 output = null;
132                 output1 = null;
133                 screenTexture = null;
134 
135                 disposedValue = true;
136             }
137         }
138 
139         // TODO: 仅当以上 Dispose(bool disposing) 拥有用于释放未托管资源的代码时才替代终结器。
140         // ~DirectXScreenCapturer()
141         // {
142         //   // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
143         //   Dispose(false);
144         // }
145 
146         // 添加此代码以正确实现可处置模式。
147         public void Dispose()
148         {
149             // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
150             Dispose(true);
151             // TODO: 如果在以上内容中替代了终结器,则取消注释以下行。
152             // GC.SuppressFinalize(this);
153         }
154         #endregion
155     }

使用示例

其中使用了窗口枚举辅助类,详细代码请看文章末尾的 Github 项目。支持 .Net Core。

 1         static async Task Main(string[] args)
 2         {
 3             Console.Write("按任意键开始DX截图……");
 4             Console.ReadKey();
 5 
 6             string path = @"E:截图测试";
 7 
 8             var cancel = new CancellationTokenSource();
 9             await Task.Run(() =>
10             {
11                 Task.Run(() =>
12                 {
13                     Thread.Sleep(5000);
14                     cancel.Cancel();
15                     Console.WriteLine("DX截图结束!");
16                 });
17                 var savePath = $@"{path}DX";
18                 Directory.CreateDirectory(savePath);
19 
20                 using var dx = new DirectXScreenCapturer();
21                 Console.WriteLine("开始DX截图……");
22                 
23                 while (!cancel.IsCancellationRequested)
24                 {
25                     var (result, isBlackFrame, image) = dx.GetFrameImage();
26                     if (result.Success && !isBlackFrame) image.Save($@"{savePath}{DateTime.Now.Ticks}.jpg", ImageFormat.Jpeg);
27                     image?.Dispose();
28                 }
29             }, cancel.Token);
30 
31             var windows = WindowEnumerator.FindAll();
32             for (int i = 0; i < windows.Count; i++)
33             {
34                 var window = windows[i];
35                 Console.WriteLine($@"{i.ToString().PadLeft(3, ' ')}. {window.Title}
36             {window.Bounds.X}, {window.Bounds.Y}, {window.Bounds.Width}, {window.Bounds.Height}");
37             }
38 
39             var savePath = $@"{path}Gdi";
40             Directory.CreateDirectory(savePath);
41             Console.WriteLine("开始Gdi窗口截图……");
42 
43             foreach (var win in windows)
44             {
45                 var image = CaptureWindow.ByHwnd(win.Hwnd);
46                 image.Save($@"{savePath}{win.Title.Substring(win.Title.LastIndexOf(@"") < 0 ? 0 : win.Title.LastIndexOf(@"") + 1).Replace("/", "").Replace("*", "").Replace("?", "").Replace(""", "").Replace(":", "").Replace("<", "").Replace(">", "").Replace("|", "")}.jpg", ImageFormat.Jpeg);
47                 image.Dispose();
48             }
49             Console.WriteLine("Gdi窗口截图结束!");
50 
51             Console.ReadKey();
52         }

结语

这个示例代码中的 DX 截图只支持 win7 以上版本,xp 是时候退出历史舞台了。代码参考了网上大神的文章,并根据实际情况进行改造,尽可能简化实现和使用代码,展示最简单情况下所必须的代码。如果实际需求比较复杂,可以以这个为底版进行改造。



Tags:Windows   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
提高Windows操作系统效率与速度的实用技巧指南
在这个快节奏的数字时代,我们都希望能够高效地完成工作和日常任务。对于广泛使用的Windows操作系统,掌握一些操作技巧不仅可以提高我们的工作效率,还能让我们的电脑运行得更加...【详细内容】
2024-04-18  Search: Windows  点击:(5)  评论:(0)  加入收藏
从Windows到MacOS:操作系统的选择与个人偏好
在数字世界的广阔天地中,操作系统作为计算机的核心软件,为用户提供了与计算机硬件交互的平台。在众多操作系统中,Windows和MacOS因其各自的特点和优势,占据了市场的主导地位。本...【详细内容】
2024-04-11  Search: Windows  点击:(11)  评论:(0)  加入收藏
如何在Windows 10中查看电脑的名称?这里提供详细步骤
你想在有多台计算机组成的网络上查找你的计算机吗?一种方法是找到你的电脑名称,然后在网络上匹配该名称。下面是如何在Windows 10中使用图形和命令行方法查看你的计算机名称。...【详细内容】
2024-04-10  Search: Windows  点击:(6)  评论:(0)  加入收藏
系统优化工具,Ultimate Windows Tweaker软件体验
电脑上的Windows优化工具年年都有,每年还会翻着花样地出现新东西,都不带重复的。每个人都可以上来折腾一番Windows...从这个角度来说,Windows系统还挺“稳定”的,经得起各种用户...【详细内容】
2024-04-10  Search: Windows  点击:(4)  评论:(0)  加入收藏
Windows 10明年结束免费支持 操作系统付费更新来了
微软宣布,Windows 10操作系统将于明年10月14日正式结束免费的技术支持。自此之后,用户想要获得更新补丁,就需要支付费用。具体而言,企业用户和个人用户将面临不同的收费标准。对...【详细内容】
2024-04-08  Search: Windows  点击:(13)  评论:(0)  加入收藏
关于Windows中AppData的相关知识,看这篇文章就可以了
如果AppData文件夹占用了你电脑上的太多空间,则需要清理AppData文件夹。下面是一些帮助你在Windows计算机上进行AppData清理的方法。什么是AppData文件夹AppData文件夹是保存...【详细内容】
2024-03-30  Search: Windows  点击:(10)  评论:(0)  加入收藏
谷歌服务现已支持使用 Windows Hello 人脸和指纹解锁登录
IT之家 3 月 28 日消息,谷歌近日对其账户登录页面进行了重大更新,现在能够在用户登录谷歌账户时,使用 Windows Hello 作为身份验证方法。使用通行密钥,用户将不再局限于使用密码...【详细内容】
2024-03-29  Search: Windows  点击:(29)  评论:(0)  加入收藏
Win11 学院:如何在 Windows 11 Build 226x1.3371 预览版锁屏中扩充财经等内容
3 月 22 日消息,微软今天发布了 Windows 11 226x1.3371 预览版更新,并邀请部分 Release Preview 频道 Windows Insider 用户,测试锁屏扩充功能。微软目前仅邀请部分用户测试,如...【详细内容】
2024-03-22  Search: Windows  点击:(29)  评论:(0)  加入收藏
苹果手机投屏到Windows电脑,帮父母轻松攻克手机难题
明窗净几夜未央,键盘轻敲解忧忙。父母笑颜消难题,孝心科技共光芒。QQ、微信、小红书等社交软件不仅年轻人在用,老年人也逐步使用社交软件建立起自己的朋友圈。但这些“新”软件...【详细内容】
2024-03-18  Search: Windows  点击:(10)  评论:(0)  加入收藏
Linux发行版 Ubuntu 迎更新 界面设计灵感来自 Windows 11
近日,一位第三方开发者推出了一款名为“Wubuntu”的特殊Linux发行版。这款系统源自主流的Ubuntu版本,但在界面设计上却借鉴了微软最新的Windows 11风格,甚至在其中融入了微软标...【详细内容】
2024-02-27  Search: Windows  点击:(54)  评论:(0)  加入收藏
▌简易百科推荐
提高Windows操作系统效率与速度的实用技巧指南
在这个快节奏的数字时代,我们都希望能够高效地完成工作和日常任务。对于广泛使用的Windows操作系统,掌握一些操作技巧不仅可以提高我们的工作效率,还能让我们的电脑运行得更加...【详细内容】
2024-04-18  千江有水千江月    Tags:Windows   点击:(5)  评论:(0)  加入收藏
Win10控制面板,这些必备知识请收好!
Win10控制面板是Windows 10操作系统中的一个重要组件,它为用户提供了一个集中管理和配置系统设置的平台。通过控制面板,用户可以轻松调整计算机的各种设置,从而优化系统性能,提...【详细内容】
2024-04-10  数据蛙恢复专家    Tags:Win10   点击:(7)  评论:(0)  加入收藏
桌面图标不见了怎么恢复?3个轻松恢复方法请记好!
在日常使用电脑的过程中,有时我们会突然发现桌面上的图标不见了,这往往会让人感到困惑和不知所措。不过,别担心,这通常只是一个小问题,有很多方法可以帮助你快速恢复桌面图标。桌...【详细内容】
2024-04-09  数据蛙恢复专家    Tags:桌面图标   点击:(9)  评论:(0)  加入收藏
Win11 24H2狠心封杀!第三方UI修改软件不能用了
快科技4月8日消息,如果你正在用一些可以修改系统UI界面、功能的第三方软件,一定不要升级Windows 11 24H2 RTM正式版,因为它们都被悄无声息地封杀了。据多位网友反馈,Windows 11...【详细内容】
2024-04-08    驱动之家  Tags:Win11   点击:(11)  评论:(0)  加入收藏
Windows 10明年结束免费支持 操作系统付费更新来了
微软宣布,Windows 10操作系统将于明年10月14日正式结束免费的技术支持。自此之后,用户想要获得更新补丁,就需要支付费用。具体而言,企业用户和个人用户将面临不同的收费标准。对...【详细内容】
2024-04-08    中关村在线  Tags:Windows   点击:(13)  评论:(0)  加入收藏
微软已修复 Win10 中 sysprep.exe 系统准备工具无法使用问题
IT之家 4 月 6 日消息,上个月,微软为 Win10 发布了最新的 KB5035941 更新,引入了新的锁屏小部件、用于桌面背景的 Windows 聚焦以及许多其他功能改进。除此之外,此次更新还解决...【详细内容】
2024-04-07    IT之家  Tags:Win10   点击:(12)  评论:(0)  加入收藏
微软 Win11 24H2 设置应用前瞻:加速去控制面板、支持sudo命令等
IT之家 4 月 5 日消息,微软正加速推进 Windows 11 24H2 更新,日前发布了 RTM 候选预览版 Build 26100 更新,而且 Windows 11 LTSC 镜像近日也偷跑现身。Windows Latest 近日发...【详细内容】
2024-04-05    IT之家  Tags:Win11   点击:(17)  评论:(0)  加入收藏
微软 Win11 新增两种键盘快捷方式,方便调整文件资源管理器列宽
IT之家 3 月 24 日消息,虽然 Windows 已经是一个非常成熟的操作系统,但微软仍会不时添加一些新的快捷键。对于用户来说,如果能更多地去了解一些常用键盘快捷键,可能对于提高办公...【详细内容】
2024-03-24    IT之家  Tags:Win11   点击:(21)  评论:(0)  加入收藏
Win11 学院:如何在 Windows 11 Build 226x1.3371 预览版锁屏中扩充财经等内容
3 月 22 日消息,微软今天发布了 Windows 11 226x1.3371 预览版更新,并邀请部分 Release Preview 频道 Windows Insider 用户,测试锁屏扩充功能。微软目前仅邀请部分用户测试,如...【详细内容】
2024-03-22    IT之家  Tags:Win11   点击:(29)  评论:(0)  加入收藏
Win10关闭自带杀毒软件教程,两招彻底把Windows Defender关闭
很多玩辅助的小伙伴电脑一般都会安装第三方杀毒软件,如360、火绒,管家等,但是Win10系统的话还自带了Windows Defender得杀毒软件,在打开一些包含EXE程序的时候,Windows Defender...【详细内容】
2024-02-26  荒野大镖客    Tags:Win10   点击:(7)  评论:(0)  加入收藏
站内最新
站内热门
站内头条