首页/文章列表/文章详情

.NET 窗口/屏幕截图

编程知识1612024-07-30评论

图像采集源除了显示控件(上一篇《.NET 控件转图片》有介绍从界面控件转图片),更多的是窗口以及屏幕。

窗口截图最常用的方法是GDI,直接上Demo吧:

 1         private void GdiCaptureButton_OnClick(object sender, RoutedEventArgs e)
 2         {
 3             var bitmap = CaptureScreen();
 4             CaptureImage.Source = ConvertBitmapToBitmapSource(bitmap);
 5         }
 6         /// <summary>
 7         /// 截图屏幕
 8         /// </summary>
 9         /// <returns></returns>
10         public static Bitmap CaptureScreen()
11         {
12             IntPtr desktopWindow = GetDesktopWindow();
13             //获取窗口位置大小
14             GetWindowRect(desktopWindow, out var lpRect);
15             return CaptureByGdi(desktopWindow, 0d, 0d, lpRect.Width, lpRect.Height);
16         }
17         private BitmapSource ConvertBitmapToBitmapSource(Bitmap bitmap)
18         {
19             using MemoryStream memoryStream = new MemoryStream();
20             // 将 System.Drawing.Bitmap 保存到内存流中
21             bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);
22             // 重置内存流的指针到开头
23             memoryStream.Seek(0, SeekOrigin.Begin);
24 
25             // 创建 BitmapImage 对象并从内存流中加载图像
26             BitmapImage bitmapImage = new BitmapImage();
27             bitmapImage.BeginInit();
28             bitmapImage.StreamSource = memoryStream;
29             bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
30             bitmapImage.EndInit();
31             // 确保内存流不会被回收
32             bitmapImage.Freeze();
33             return bitmapImage;
34         }
35         /// <summary>
36         /// 截图窗口/屏幕
37         /// </summary>
38         /// <param name="windowIntPtr">窗口句柄(窗口或者桌面)</param>
39         /// <param name="left">水平坐标</param>
40         /// <param name="top">竖直坐标</param>
41         /// <param name="width">宽度</param>
42         /// <param name="height">高度</param>
43         /// <returns></returns>
44         private static Bitmap CaptureByGdi(IntPtr windowIntPtr, double left, double top, double width, double height)
45         {
46             IntPtr windowDc = GetWindowDC(windowIntPtr);
47             IntPtr compatibleDc = CreateCompatibleDC(windowDc);
48             IntPtr compatibleBitmap = CreateCompatibleBitmap(windowDc, (int)width, (int)height);
49             IntPtr bitmapObj = SelectObject(compatibleDc, compatibleBitmap);
50             BitBlt(compatibleDc, 0, 0, (int)width, (int)height, windowDc, (int)left, (int)top, CopyPixelOperation.SourceCopy);
51             Bitmap bitmap = System.Drawing.Image.FromHbitmap(compatibleBitmap);
52             //释放
53             SelectObject(compatibleDc, bitmapObj);
54             DeleteObject(compatibleBitmap);
55             DeleteDC(compatibleDc);
56             ReleaseDC(windowIntPtr, windowDc);
57             return bitmap;
58         }

根据user32.dll下拿到的桌面信息-句柄获取桌面窗口的设备上下文,再以设备上下文分别创建内存设备上下文、设备位图句柄

1BOOL BitBlt(2 HDC hdcDest, //目标设备上下文3intnXDest,//目标起始x坐标4intnYDest,//目标起始y坐标5intnWidth,//宽度(像素)6intnHeight,//高度(像素)7 HDC hdcSrc, //源设备上下文8intnXSrc,//源起始x坐标9intnYSrc,//源起始y坐标10 DWORD dwRop //操作码(如CopyPixelOperation.SourceCopy)11);

图像位块传输BitBlt是最关键的函数,Gdi提供用于在设备上下文之间进行位图块的传输,从原设备上下文复现位图到创建的设备上下文

另外,与Bitblt差不多的还有StretchBlt,StretchBlt也是复制图像,但可以同时对图像进行拉伸或者缩小,需要缩略图可以用这个方法

然后以设备位图句柄输出一个位图System.Drawing.Bitmap,使用到的User32、Gdi32函数:

1///<summary>2///获取桌面窗口3///</summary>4///<returns></returns>5[DllImport("user32.dll")]6publicstaticextern IntPtr GetDesktopWindow();7///<summary>8///获取整个窗口的矩形区域9///</summary>10///<returns></returns>11[DllImport("user32.dll", SetLastError = true)]12publicstaticexternbool GetWindowRect(IntPtr hwnd, out RECT lpRect);13///<summary>14///检索整个窗口的设备上下文15///</summary>16///<param name="hWnd">具有要检索的设备上下文的窗口的句柄</param>17///<returns></returns>18[DllImport("user32.dll", SetLastError = true)]19publicstaticextern IntPtr GetWindowDC(IntPtr hWnd);20///<summary>21///创建与指定设备兼容的内存设备上下文22///</summary>23///<param name="hdc">现有 DC 的句柄</param>24///<returns>如果函数成功,则返回值是内存 DC 的句柄,否则返回Null</returns>25[DllImport("gdi32.dll")]26publicstaticextern IntPtr CreateCompatibleDC([In] IntPtr hdc);27///<summary>28///将对象选择到指定的设备上下文中29///</summary>30///<param name="hdc">DC 的句柄</param>31///<param name="gdiObj">要选择的对象句柄</param>32///<returns>如果函数成功,则返回值是兼容位图 (DDB) 的句柄,否则返回Null</returns>33[DllImport("gdi32.dll")]34publicstaticextern IntPtr SelectObject([In] IntPtr hdc, [In] IntPtr gdiObj);35///<summary>36///创建与与指定设备上下文关联的设备的位图37///</summary>38///<param name="hdc">设备上下文的句柄</param>39///<param name="nWidth">位图宽度(以像素为单位)</param>40///<param name="nHeight">位图高度(以像素为单位)</param>41///<returns></returns>42[DllImport("gdi32.dll")]43publicstaticextern IntPtr CreateCompatibleBitmap([In] IntPtr hdc, intnWidth,intnHeight);44///<summary>45///执行与从指定源设备上下文到目标设备上下文中的像素矩形对应的颜色数据的位块传输46///</summary>47///<param name="hdcDest">目标设备上下文的句柄</param>48///<param name="xDest">目标矩形左上角的 x 坐标(逻辑单位)</param>49///<param name="yDest">目标矩形左上角的 y 坐标(逻辑单位)</param>50///<param name="wDest">源矩形和目标矩形的宽度(逻辑单位)</param>51///<param name="hDest">源矩形和目标矩形的高度(逻辑单位)</param>52///<param name="hdcSource">源设备上下文的句柄</param>53///<param name="xSrc">源矩形左上角的 x 坐标(逻辑单位)</param>54///<param name="ySrc">源矩形左上角的 y 坐标(逻辑单位)</param>55///<param name="rop">定义如何将源矩形的颜色数据与目标矩形的颜色数据相结合</param>56///<returns></returns>57[DllImport("gdi32.dll")]58publicstaticexternbool BitBlt(IntPtr hdcDest,59intxDest,intyDest,intwDest,int hDest, IntPtr hdcSource,60intxSrc,int ySrc, CopyPixelOperation rop);61///<summary>62///删除逻辑笔、画笔、字体、位图、区域或调色板,释放与对象关联的所有系统资源。63///删除对象后,指定的句柄将不再有效。64///</summary>65///<param name="hObject"></param>66///<returns></returns>67[DllImport("gdi32.dll")]68publicstaticexternbool DeleteObject(IntPtr hObject);69///<summary>70///删除指定的设备上下文71///</summary>72///<param name="hdc">设备上下文的句设备上下文的句</param>73///<returns></returns>74[DllImport("gdi32.dll")]75publicstaticexternbool DeleteDC([In] IntPtr hdc);76///<summary>77/// 释放设备上下文 (DC),释放它以供其他应用程序使用78///</summary>79///<param name="hWnd"></param>80///<param name="hdc"></param>81///<returns></returns>82[DllImport("user32.dll", SetLastError = true)]83publicstaticexternbool ReleaseDC(IntPtr hWnd, IntPtr hdc);8485///<summary>86///定义一个矩形区域。87///</summary>88[StructLayout(LayoutKind.Sequential)]89publicstructRECT90{91///<summary>92///矩形左侧的X坐标。93///</summary>94publicintLeft;9596///<summary>97///矩形顶部的Y坐标。98///</summary>99publicintTop;100101///<summary>102///矩形右侧的X坐标。103///</summary>104publicintRight;105106///<summary>107///矩形底部的Y坐标。108///</summary>109publicintBottom;110111///<summary>112///获取矩形的宽度。113///</summary>114publicint Width => Right -Left;115116///<summary>117///获取矩形的高度。118///</summary>119publicint Height => Bottom -Top;120121///<summary>122///初始化一个新的矩形。123///</summary>124///<param name="left">矩形左侧的X坐标。</param>125///<param name="top">矩形顶部的Y坐标。</param>126///<param name="right">矩形右侧的X坐标。</param>127///<param name="bottom">矩形底部的Y坐标。</param>128publicRECT(intleft,inttop,intright,intbottom)129{130 Left =left;131 Top =top;132 Right =right;133 Bottom =bottom;134}135}
View Code

还有一种比较简单的方法Graphics.CopyFromScreen,看看调用DEMO:

1privatevoidGraphicsCaptureButton_OnClick(object sender, RoutedEventArgs e)2{3var image =CaptureScreen1();4 CaptureImage.Source =ConvertBitmapToBitmapSource(image);5}6///<summary>7///截图屏幕8///</summary>9///<returns></returns>10publicstatic Bitmap CaptureScreen1()11{12 IntPtr desktopWindow =GetDesktopWindow();13//获取窗口位置大小14GetWindowRect(desktopWindow,outvarlpRect);15returnCaptureScreenByGraphics(0,0, lpRect.Width, lpRect.Height);16}17///<summary>18///截图屏幕19///</summary>20///<param name="x">x坐标</param>21///<param name="y">y坐标</param>22///<param name="width">截取的宽度</param>23///<param name="height">截取的高度</param>24///<returns></returns>25publicstatic Bitmap CaptureScreenByGraphics(intx,inty,intwidth,intheight)26{27var bitmap = new Bitmap(width, height);28usingvar graphics =Graphics.FromImage(bitmap);29 graphics.CopyFromScreen(x, y, 0,0,new System.Drawing.Size(width, height), CopyPixelOperation.SourceCopy);30returnbitmap;31}

Graphics.CopyFromScreen调用简单了很多,与GDI有什么区别?

Graphics.CopyFromScreen内部也是通过GDI.BitBlt来完成屏幕捕获的,封装了下提供更高级别、易胜的API。

测试了下,第一种方法Gdi32性能比Graphics.CopyFromScreen性能略微好一点,冷启动时更明显点,试了2次耗时大概少个10多ms。

所以对于一般应用场景,使用 Graphics.CopyFromScreen 就足够了,但如果你需要更高的控制权和性能优化,建议使用 Gdi32.BitBlt

 kybs00/CaptureImageDemo (github.com)

博客园

这个人很懒...

用户评论 (0)

发表评论

captcha