少俠學截圖-C#螢幕捕捉的方式

iDotNetSpace發表於2009-12-15

本篇主要介紹如何通過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;
        }

經使用,完美解決問題。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-622490/,如需轉載,請註明出處,否則將追究法律責任。

相關文章