.NET 窗口/屏幕截图 - 唐宋元明清2188 - 博客园

来源: .NET 窗口/屏幕截图 – 唐宋元明清2188 – 博客园

图像采集源除了显示控件(上一篇《.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下拿到的桌面信息-句柄获取桌面窗口的设备上下文,再以设备上下文分别创建内存设备上下文、设备位图句柄

复制代码
 1 BOOL BitBlt(
 2     HDC   hdcDest,  // 目标设备上下文
 3     int   nXDest,   // 目标起始x坐标
 4     int   nYDest,   // 目标起始y坐标
 5     int   nWidth,   // 宽度(像素)
 6     int   nHeight,  // 高度(像素)
 7     HDC   hdcSrc,   // 源设备上下文
 8     int   nXSrc,    // 源起始x坐标
 9     int   nYSrc,    // 源起始y坐标
10     DWORD dwRop    // 操作码(如CopyPixelOperation.SourceCopy)
11 );
复制代码

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

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

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

 View Code

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

复制代码
 1         private void GraphicsCaptureButton_OnClick(object sender, RoutedEventArgs e)
 2         {
 3             var image = CaptureScreen1();
 4             CaptureImage.Source = ConvertBitmapToBitmapSource(image);
 5         }
 6         /// <summary>
 7         /// 截图屏幕
 8         /// </summary>
 9         /// <returns></returns>
10         public static Bitmap CaptureScreen1()
11         {
12             IntPtr desktopWindow = GetDesktopWindow();
13             //获取窗口位置大小
14             GetWindowRect(desktopWindow, out var lpRect);
15             return CaptureScreenByGraphics(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>
25         public static Bitmap CaptureScreenByGraphics(int x, int y, int width, int height)
26         {
27             var bitmap = new Bitmap(width, height);
28             using var graphics = Graphics.FromImage(bitmap);
29             graphics.CopyFromScreen(x, y, 0, 0, new System.Drawing.Size(width, height), CopyPixelOperation.SourceCopy);
30             return bitmap;
31         }
复制代码

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

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

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

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

kybs00/CaptureImageDemo (github.com)

赞(1) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏