来源: .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