.NET 窗口/屏幕截图
图像采集源除了显示控件(上一篇《
.NET 控件转图片
》有介绍从界面控件转图片),更多的是窗口以及屏幕。
窗口截图最常用的方法是GDI,直接上Demo吧:
1 private void GdiCaptureButton_OnClick(objectsender, RoutedEventArgs e)2 {3 var bitmap =CaptureScreen();4 CaptureImage.Source =ConvertBitmapToBitmapSource(bitmap);5 }6 /// <summary> 7 ///截图屏幕8 /// </summary> 9 /// <returns></returns> 10 public staticBitmap CaptureScreen()11 {12 IntPtr desktopWindow =GetDesktopWindow();13 //获取窗口位置大小 14 GetWindowRect(desktopWindow, out varlpRect);15 returnCaptureByGdi(desktopWindow, 0d, 0d, lpRect.Width, lpRect.Height);16 }17 privateBitmapSource ConvertBitmapToBitmapSource(Bitmap bitmap)18 {19 using MemoryStream memoryStream = newMemoryStream();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 = newBitmapImage();27 bitmapImage.BeginInit();28 bitmapImage.StreamSource =memoryStream;29 bitmapImage.CacheOption =BitmapCacheOption.OnLoad;30 bitmapImage.EndInit();31 //确保内存流不会被回收 32 bitmapImage.Freeze();33 returnbitmapImage;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, doubleheight)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 returnbitmap;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函数:
1 /// <summary> 2 ///获取桌面窗口3 /// </summary> 4 /// <returns></returns> 5 [DllImport("user32.dll")]6 public static externIntPtr GetDesktopWindow();7 /// <summary> 8 ///获取整个窗口的矩形区域9 /// </summary> 10 /// <returns></returns> 11 [DllImport("user32.dll", SetLastError = true)]12 public static extern bool GetWindowRect(IntPtr hwnd, outRECT lpRect);13 /// <summary> 14 ///检索整个窗口的设备上下文15 /// </summary> 16 /// <param name="hWnd">具有要检索的设备上下文的窗口的句柄</param> 17 /// <returns></returns> 18 [DllImport("user32.dll", SetLastError = true)]19 public static externIntPtr GetWindowDC(IntPtr hWnd);20 /// <summary> 21 ///创建与指定设备兼容的内存设备上下文22 /// </summary> 23 /// <param name="hdc">现有 DC 的句柄</param> 24 /// <returns>如果函数成功,则返回值是内存 DC 的句柄,否则返回Null</returns> 25 [DllImport("gdi32.dll")]26 public static externIntPtr 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")]34 public static externIntPtr 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")]43 public static extern IntPtr CreateCompatibleBitmap([In] IntPtr hdc, int nWidth, 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")]58 public static extern boolBitBlt(IntPtr hdcDest,59 int xDest, int yDest, int wDest, inthDest, IntPtr hdcSource,60 int xSrc, intySrc, CopyPixelOperation rop);61 /// <summary> 62 ///删除逻辑笔、画笔、字体、位图、区域或调色板,释放与对象关联的所有系统资源。63 ///删除对象后,指定的句柄将不再有效。64 /// </summary> 65 /// <param name="hObject"></param> 66 /// <returns></returns> 67 [DllImport("gdi32.dll")]68 public static extern boolDeleteObject(IntPtr hObject);69 /// <summary> 70 ///删除指定的设备上下文71 /// </summary> 72 /// <param name="hdc">设备上下文的句设备上下文的句</param> 73 /// <returns></returns> 74 [DllImport("gdi32.dll")]75 public static extern boolDeleteDC([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)]83 public static extern boolReleaseDC(IntPtr hWnd, IntPtr hdc);84 85 /// <summary> 86 ///定义一个矩形区域。87 /// </summary> 88 [StructLayout(LayoutKind.Sequential)]89 public structRECT90 {91 /// <summary> 92 ///矩形左侧的X坐标。93 /// </summary> 94 public intLeft;95 96 /// <summary> 97 ///矩形顶部的Y坐标。98 /// </summary> 99 public intTop;100 101 /// <summary> 102 ///矩形右侧的X坐标。103 /// </summary> 104 public intRight;105 106 /// <summary> 107 ///矩形底部的Y坐标。108 /// </summary> 109 public intBottom;110 111 /// <summary> 112 ///获取矩形的宽度。113 /// </summary> 114 public int Width => Right -Left;115 116 /// <summary> 117 ///获取矩形的高度。118 /// </summary> 119 public int Height => Bottom -Top;120 121 /// <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> 128 public RECT(int left, int top, int right, intbottom)129 {130 Left =left;131 Top =top;132 Right =right;133 Bottom =bottom;134 }135 }
View Code
还有一种比较简单的方法Graphics.CopyFromScreen,看看调用DEMO:
1 private void GraphicsCaptureButton_OnClick(objectsender, RoutedEventArgs e)2 {3 var image =CaptureScreen1();4 CaptureImage.Source =ConvertBitmapToBitmapSource(image);5 }6 /// <summary> 7 ///截图屏幕8 /// </summary> 9 /// <returns></returns> 10 public staticBitmap CaptureScreen1()11 {12 IntPtr desktopWindow =GetDesktopWindow();13 //获取窗口位置大小 14 GetWindowRect(desktopWindow, out varlpRect);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, intheight)26 {27 var bitmap = newBitmap(width, height);28 using var graphics =Graphics.FromImage(bitmap);29 graphics.CopyFromScreen(x, y, 0, 0, newSystem.Drawing.Size(width, height), CopyPixelOperation.SourceCopy);30 returnbitmap;31 }
Graphics.CopyFromScreen调用简单了很多,与GDI有什么区别?
Graphics.CopyFromScreen内部也是通过GDI.BitBlt来完成屏幕捕获的,封装了下提供更高级别、易胜的API。
测试了下,第一种方法Gdi32性能比Graphics.CopyFromScreen性能略微好一点,冷启动时更明显点,试了2次耗时大概少个10多ms。
所以对于一般应用场景,使用 Graphics.CopyFromScreen 就足够了,但如果你需要更高的控制权和性能优化,建议使用 Gdi32.BitBlt