少侠学截屏-C#屏幕捕捉的方式

简介:

本篇主要介绍如何通过C#代码来获得Windows操作系统的桌面位图。

当然,不仅仅是截屏。主要是受园子里的朋友我不是圣人的激发,勾起了继续探究一下Windows屏幕捕捉和网络传输的欲望。

以前也搞过一阵子,不过都是浅尝一下。最近几天搞了点眉目出来,这里就先发第一块出来分享一下。

后续的还要等我调试完成,想清楚了再发。

截屏的结果就是要获得一个位图

主要方式有两个:

1、C#类库

2、Windows32API

先介绍使用C#类库的方式

主要使用Screen类
http://msdn.microsoft.com/zh-cn/library/system.windows.forms.screen.aspx

表示单个系统上的一个或多个显示设备。

命名空间: System.Windows.Forms
程序集: System.Windows.Forms(在 System.Windows.Forms.dll 中)

代码:

Image img = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
Graphics g = Graphics.FromImage(img);
g.CopyFromScreen(new Point(0, 0), new Point(0, 0), Screen.PrimaryScreen.Bounds.Size);

通过这3行,img已经获得了对一个位图的引用了。

简单得很哦

 

第二种:使用Win32API

主要参考这个版本的代码

http://www.csharphelp.com/2006/11/capturing-the-screen-image-using-c/

具体不想多做介绍了

简单说明,Windows操作系统本身拥有丰富的代码,能完成很多系统级功能,并且非常高效。
而C#以及各种.net程序,以及其他高级语言完成类似功能时都需要程序员为之编码,且运行效率多数是不行的。
所以干吗要重复造轮子呢

在.net这里可以使用上述的方式来引用Windows的系统函数来完成我们需要的功能

探讨的部分是,在很多Win32调用的程序中,释放句柄一直是非常要紧的一件事
但是在我实际使用中发现不是必须如此的

原始代码:

public static Bitmap GetDesktopImage()
{

//Variable to keep the handle of the btimap.
IntPtr m_HBitmap=null;

//Variable to keep the refrence to the desktop bitmap.
System.Drawing.Bitmap bmp=null;

//In size variable we shall keep the size of the screen.
SIZE size;

//Here we get the handle to the desktop device context.
IntPtr hDC = PlatformInvokeUSER32.GetDC(PlatformInvokeUSER32.GetDesktopWindow());

//Here we make a compatible device context in memory for screen device context.
IntPtr hMemDC = PlatformInvokeGDI32.CreateCompatibleDC(hDC);

//We pass SM_CXSCREEN constant to GetSystemMetrics to get the X coordinates of screen.
size.cx = PlatformInvokeUSER32.GetSystemMetrics (PlatformInvokeUSER32.SM_CXSCREEN);

//We pass SM_CYSCREEN constant to GetSystemMetrics to get the Y coordinates of screen.
size.cy = PlatformInvokeUSER32.GetSystemMetrics(PlatformInvokeUSER32.SM_CYSCREEN);

//We create a compatible bitmap of screen size and using screen device context.
m_HBitmap = PlatformInvokeGDI32.CreateCompatibleBitmap(hDC, size.cx, size.cy);

//As m_HBitmap is IntPtr we can not check it against null. For this purspose IntPtr.Zero is used.
if (m_HBitmap!=IntPtr.Zero)
{
//Here we select the compatible bitmap in memeory device context and keeps the refrence to Old bitmap.
IntPtr hOld = (IntPtr) PlatformInvokeGDI32.SelectObject(hMemDC, m_HBitmap);

//We copy the Bitmap to the memory device context.
PlatformInvokeGDI32.BitBlt(hMemDC, 0, 0,size.cx,size.cy, hDC, 0, 0,PlatformInvokeGDI32.SRCCOPY);

//We select the old bitmap back to the memory device context.
PlatformInvokeGDI32.SelectObject(hMemDC, hOld);

//We delete the memory device context.
PlatformInvokeGDI32.DeleteDC(hMemDC);

//We release the screen device context.
PlatformInvokeUSER32.ReleaseDC(PlatformInvokeUSER32.GetDesktopWindow(), hDC);

//Image is created by Image bitmap handle and assigned to Bitmap variable.
bmp=System.Drawing.Image.FromHbitmap(m_HBitmap);

//Delete the compatible bitmap object.
PlatformInvokeGDI32.DeleteObject(m_HBitmap);

return bmp;
}
//If m_HBitmap is null retunrn null.
return null;
}

对比代码:

        public static Bitmap GetDesktopImage()
        {   

            //As m_HBitmap is IntPtr we can not check it against null. For this purspose IntPtr.Zero is used.
            if (m_HBitmap!=IntPtr.Zero)
            {
                //Here we select the compatible bitmap in memeory device context and keeps the refrence to Old bitmap.
                IntPtr hOld = (IntPtr) PlatformInvokeGDI32.SelectObject(hMemDC, m_HBitmap);

                //We copy the Bitmap to the memory device context.
                PlatformInvokeGDI32.BitBlt(hMemDC, 0, 0,size.cx,size.cy, hDC, 0, 0, PlatformInvokeGDI32.SRCCOPY);

                //We select the old bitmap back to the memory device context.
                PlatformInvokeGDI32.SelectObject(hMemDC, hOld);


                //老外的原始代码中,静态构造里是没东西的。所有那些都是在本函数中声明、使用并且销毁的。
                //如果有喜欢销毁东西癖好的,可以在这里销毁hDC、hMemDC以及m_HBitmap
                //不过我觉得没有这个必要,并且不销毁能提高点点速度


                return System.Drawing.Image.FromHbitmap(m_HBitmap);
            }
            //If m_HBitmap is null retunrn null.
            return null;
        }

 

因为我觉得hDC在本程序使用中永远是指向桌面窗口的句柄,简单说这个指针的值是永远不变的。除非桌面窗口被释放、重建。
同理hMemDC也是一样和桌面窗口关联的
m_HBitmap则是一个位图对象的引用,也就是申请了一块内存。
反复多次对同一片内存写入数据是不会有问题的

因此我在代码中把这几个的初始化都转移到了静态构造里,只运行一次。实际看速度是快了一点的。

由于对C/C++的理解不是很深(本人本质是VB5.0+VBA office程序员出身),希望有关达人能指点一下上述理由是否成立。

小结:

C#类库方式:代码简洁、学习代价低

Win32API方式:代码复杂、学习难度高

一般而言用C#方式,并且执行效率上看,在我的机器上两者区别不大。Win32API方式略有优势。

关键点:鼠标呢?

上面两个代码其实都没有捕捉到鼠标,很不爽的一个地方

通过网络搜索和试验,在Win32API方式下我完成了对鼠标形状及位置的捕获。Win32API在win平台上应该是万能的,呵呵。

总体思路是在捕获全屏后,单独获取鼠标位置及形状。然后“画”到先前捕获的位图上

        public static Bitmap GetDesktopImage()
        {   

            //As m_HBitmap is IntPtr we can not check it against null. For this purspose IntPtr.Zero is used.
            if (m_HBitmap!=IntPtr.Zero)
            {
                //Here we select the compatible bitmap in memeory device context and keeps the refrence to Old bitmap.
                IntPtr hOld = (IntPtr) PlatformInvokeGDI32.SelectObject(hMemDC, m_HBitmap);

                //We copy the Bitmap to the memory device context.
                PlatformInvokeGDI32.BitBlt(hMemDC, 0, 0,size.cx,size.cy, hDC, 0, 0, PlatformInvokeGDI32.SRCCOPY);

                #region 绘制鼠标
                PlatformInvokeUSER32.CURSORINFO pci = new PlatformInvokeUSER32.CURSORINFO();
                pci.cbSize = Marshal.SizeOf(pci);
                PlatformInvokeUSER32.GetCursorInfo(out pci);
                IntPtr dc = hMemDC;
                //这个偏移量是我在使用时发现的问题,不一定是10,没有精确论证过。只是目前看起来位置正确。
                PlatformInvokeUSER32.DrawIconEx(dc, pci.ptScreenPos.X-10, pci.ptScreenPos.Y-10, pci.hCursor, 32, 32, 1, IntPtr.Zero, PlatformInvokeUSER32.DI_NORMAL);
                #endregion

                //We select the old bitmap back to the memory device context.
                PlatformInvokeGDI32.SelectObject(hMemDC, hOld);
                //老外的原始代码中,静态构造里是没东西的。所有那些都是在本函数中声明、使用并且销毁的。
                //如果有喜欢销毁东西癖好的,可以在这里销毁hDC、hMemDC以及m_HBitmap
                //不过我觉得没有这个必要,并且不销毁能提高点点速度               

                return System.Drawing.Image.FromHbitmap(m_HBitmap);
            }
            //If m_HBitmap is null retunrn null.
            return null;
        }

经使用,完美解决问题。

最后把完整的抓屏解决方案的代码打包放这里带鼠标的全屏抓图

谢谢观赏

作者: 徐少侠
出处: http://www.cnblogs.com/Chinese-xu/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
如有问题,可以通过 Chinese_Xu@126.com 联系我,非常感谢。

分享家:Addthis中文版
分类: .Net, C#
标签: C#, 算法, 截屏, win32API

本文转自徐少侠博客园博客,原文链接:http://www.cnblogs.com/Chinese-xu/archive/2009/12/15/1624468.html,如需转载请自行联系原作者
目录
相关文章
|
C# 编解码
WPF C# 多屏情况下,实现窗体显示到指定的屏幕内
原文:WPF C# 多屏情况下,实现窗体显示到指定的屏幕内 针对于一个程序,需要在两个显示屏上显示不同的窗体,(亦或N个显示屏N个窗体),可以使用如下的方式实现。
4963 0
|
C# 开发者
推荐一款C#开源的操作简单、免费的屏幕录制和GIF动画制作神器
推荐一款C#开源的操作简单、免费的屏幕录制和GIF动画制作神器
|
编解码 C# 图形学
C# 获取当前屏幕DPI
原文:C# 获取当前屏幕DPI 1.通过Graphics类获取 Graphics currentGraphics = Graphics.FromHwnd(new WindowInteropHelper(mainWindow).Handle); double dpixRatio = currentGraphics.DpiX/96; 比如当前屏幕设置DPI设置1.5倍,可以通过如上通过后台获取。
2981 0
|
API C#
用C#实现屏幕吸色功能,附逻辑思维讲解图,功能代码不超过50行即可实现
此程序是我上学的时候写的,好几年前的事了,前几天整理硬盘文件时发现自已其实还写过很多东西,当时还没有在园子里面混,故没怎么分享,现在有时间那就给需要的朋友分享分享,我的主要实现思路是: 一、创建一个画布(即为Form),大小和当前屏幕大小一样 二、在这快画布上建立一个绘图对象,截取复制当前屏幕内...
1264 0
C# 获取当前屏幕的宽高和位置
原文:C# 获取当前屏幕的宽高和位置 上一篇博客《C# 获取当前屏幕DPI》,介绍了如何获取当前屏幕的DPI设置 本章主要介绍如何获取当前窗口所在屏幕的信息 当前屏幕信息 如果当前是单屏幕,可以直接获取主屏幕 var primaryScreen = Screen.
1294 0
|
C# 编解码 图形学
C# 获取当前屏幕的宽高和位置
上一篇博客《C# 获取当前屏幕DPI》,介绍了如何获取当前屏幕的DPI设置 本章主要介绍如何获取当前窗口所在屏幕的信息 当前屏幕信息 如果当前是单屏幕,可以直接获取主屏幕 var primaryScreen = Screen.
2048 0
|
编解码 C# 图形学
C# 获取当前屏幕DPI
1.通过Graphics类获取 Graphics currentGraphics = Graphics.FromHwnd(new WindowInteropHelper(mainWindow).Handle); double dpixRatio = currentGraphics.DpiX/96; 比如当前屏幕设置DPI设置1.5倍,可以通过如上通过后台获取。
1248 0
|
C# 编译器
c# 处理空白字符,空白字符是指在屏幕不会显示出来的字符
空白字符是指在屏幕不会显示出来的字符(如空格,制表符tab,回车换行等)。空格、制表符、换行符、回车、换页垂直制表符和换行符称为 “空白字符”,因为它们为与间距单词和行在打印的页 )的用途可以读取更加轻松。
1295 0
C# 获取屏幕的大小
WinForm: int iActulaWidth = Screen.PrimaryScreen.Bounds.Width; int iActulaHeight = Screen.
874 0