標籤:des style blog http color 使用
我知道,標題不響亮一點你們是不會點進來看的(奸笑),好了言歸正傳,博主一直都想自己寫一個螢幕錄影軟體,相信大家都用過螢幕錄影軟體了,專業層級或者商業層級的螢幕錄影軟體都是自己寫驅動來擷取顯卡資料,或者自己寫 Hook 來勾住一些圖形函數來擷取圖形資料等等,不過博主沒有這個能耐,唯一可以用的就是 Windows 內建的 GDI 函數了,以前看過一本遊戲開發相關的書籍,裡面講解了如何使用 GetDIBits 函數來快速擷取 HDC 裡面的顏色資料,比起 GetPixel 快了上百個數量級不止,博主靈光一閃,那麼是不是意味著只要我能夠擷取任意的表單的 HDC,就可以用 GetDIBits 來擷取表單的顏色資料呢,基於這個念頭,博主開始實施這個螢幕錄影軟體,當然,實踐出真知,最終結果令人大吃一驚 —— 我成功了 !!我擷取了目標表單的顏色資料,然後儲存到檔案,然後通過一些代碼進行錄影回放,進而實現了一個簡易的螢幕錄影軟體,接下來,讓咱們看看整個軟體的實現過程。
首先,我們來大概預覽一下整個螢幕錄影軟體涉及到的技術點,然後逐一實現:
1. 如何擷取全部視窗的資訊,包括:控制代碼、HDC、標題、風格、表單大小、客戶區大小等等
2. 如何從目標表單的 HDC 裡面快速擷取顏色資料
3. 如何將顏色資料快速繪製到預覽地區
4. 如何將顏色資料儲存到檔案,並且進行錄影回放
好,接下來咱們一個一個問題來解決,首先是第一個,擷取全部視窗的資訊,這個很簡單,用 EnumWindows 函數就可以了,具體實現如下:
1 // 2 // 表單資訊 3 // 4 struct WINDOW_INFO 5 { 6 HWND handle; 7 DWORD style; 8 DWORD style_ex; 9 RECT rect_client;10 RECT rect_window;11 core::stringW title;12 core::stringW class_name;13 };14 15 unsigned int enumWindow( core::list< WINDOW_INFO > & window_list )16 {17 EnumWindows( EnumThreadWndProc, ( LPARAM ) & window_list );18 19 return window_list.size( );20 }21 22 BOOL CALLBACK EnumThreadWndProc( HWND hwnd, LPARAM lparam )23 {24 WINDOW_INFO wi = { 0 };25 core::list< WINDOW_INFO > * window_list = ( core::list< WINDOW_INFO > * ) lparam;26 27 wi.handle = hwnd;28 wi.style = GetWindowLong( hwnd, GWL_STYLE );29 wi.style_ex = GetWindowLong( hwnd, GWL_EXSTYLE );30 31 wi.title.alloc( 1024, true );32 GetWindowTextW( hwnd, wi.title.getBuffer( ), 1024 );33 34 wi.class_name.alloc( 1024, true );35 GetClassNameW( hwnd, wi.class_name.getBuffer( ), 1024 );36 37 GetClientRect( hwnd, & wi.rect_client );38 GetWindowRect( hwnd, & wi.rect_window );39 40 if( window_list ) window_list->pushTail( wi );41 42 return TRUE;43 }
在上面代碼中,WINDOW_INFO 結構體包含了表單的必須的一些資訊,當然,你可以自己擴充一下這個結構體,比如加上 HDC 啊、子控制項列表之類的;
enumWindow( ) 函數調用 Windows 的 EnumWindows( ) 函數,然後每當系統發現一個表單,都會調用一次 EnumThreadWndProc( ) 回呼函數,並且把表單的控制代碼作為參數傳遞進去,這樣子,我們就可以根據這個表單控制代碼來擷取表單資料了,在這個例子裡面,我用一個 list 鏈表儲存了全部表單的資訊,這個 list 是我自己寫的,所以和 STL 的 std::list 肯定有很大出入,大家不用糾結,知道是鏈表就可以了;
接下來就是怎麼擷取目標表單的顏色資料了,假設我們要對案頭進行錄影,那麼擷取案頭的 HWND 控制代碼就是 GetDesktopWindow( ) 函數,然後用 GetDC( ) 或者 GetWindowDC( ) 都可以拿到案頭的 HDC,接下來就是重點了 —— 使用 GetDIBits( ) 函數快速擷取案頭 HDC 裡面的顏色資料;
快速擷取案頭 DC 的顏色資料的思路是這樣子的,建立一個與案頭相互相容的 HDC,稱之為相容 DC,建立一個與相容 DC 互相相容的位元影像對象,稱之為相容位元影像,將相容位元影像選入到相容 DC 內,然後把案頭 DC 的內容,通過 BitBlt( ) 函數快速傳送到相容 DC 內,然後通過 GetDIBits( ) 函數從相容 DC 裡面擷取到顏色資料,這些顏色資料其實就是案頭的顏色資料了,說了這麼多,還是直接上代碼來的直觀:
1 struct CAPTURE_INFO_INTERNAL 2 { 3 HDC dlg_dc; 4 HWND target_wnd; 5 HDC target_dc; 6 HDC comp_dc; 7 HBITMAP comp_bmp; 8 HBITMAP old_bmp; 9 int width;10 int height;11 BITMAPINFO bmp_info;12 BYTE * buf;13 };14 15 bool prepareForRecord( CAPTURE_INFO_INTERNAL & cii )16 {17 BITMAPINFO bi = { 0 };18 19 cii.width = int( wi->rect_client.right - wi->rect_client.left );20 cii.height = int( wi->rect_client.bottom - wi->rect_client.top );21 22 cii.target_wnd = wi->handle;23 if( NULL == cii.target_wnd )24 return false;25 26 cii.target_dc = GetDC( cii.target_wnd );27 if( NULL == cii.target_dc )28 return false;29 30 cii.comp_dc = CreateCompatibleDC( cii.target_dc );31 if( NULL == cii.comp_dc )32 return false;33 34 cii.comp_bmp = CreateCompatibleBitmap(35 cii.target_dc,36 cii.width,37 cii.height );38 if( NULL == cii.comp_bmp )39 return false;40 41 cii.old_bmp = ( HBITMAP ) SelectObject(42 cii.comp_dc,43 cii.comp_bmp );44 45 cii.bmp_info.bmiHeader.biSize = sizeof( BITMAPINFOHEADER );46 cii.bmp_info.bmiHeader.biBitCount = 24;47 cii.bmp_info.bmiHeader.biCompression = BI_RGB;48 cii.bmp_info.bmiHeader.biWidth = cii.width;49 cii.bmp_info.bmiHeader.biHeight = - cii.height;50 cii.bmp_info.bmiHeader.biPlanes = 1;51 52 cii.bmp_info.bmiColors[ 0 ].rgbBlue = 8;53 cii.bmp_info.bmiColors[ 0 ].rgbGreen = 8;54 cii.bmp_info.bmiColors[ 0 ].rgbRed = 8;55 cii.bmp_info.bmiColors[ 0 ].rgbReserved = 0;56 57 bi.bmiHeader.biSize = sizeof( BITMAPINFOHEADER );58 bi.bmiHeader.biBitCount = 24;59 bi.bmiHeader.biCompression = BI_RGB;60 bi.bmiHeader.biWidth = cii.width;61 bi.bmiHeader.biHeight = - cii.height;62 bi.bmiHeader.biPlanes = 1;63 64 bi.bmiColors[ 0 ].rgbBlue = 8;65 bi.bmiColors[ 0 ].rgbGreen = 8;66 bi.bmiColors[ 0 ].rgbRed = 8;67 bi.bmiColors[ 0 ].rgbReserved = 0;68 69 GetDIBits( cii.comp_dc, cii.comp_bmp, 0, cii.height, NULL, & bi, DIB_RGB_COLORS );70 71 cii.buf = new BYTE[ bi.bmiHeader.biSizeImage ];72 73 return true;74 }
上面代碼中,CAPTURE_INFO_INTERNAL 結構體用來儲存一些必須的變數,wi 變數儲存了一個 WINDOW_INFO 表單資訊結構體,這個結構體我在枚舉表單的那一段已經講解過了,prepareForRecord( ) 函數用於初始化 CAPTURE_INFO_INTERNAL 結構體,接下來我會在定時器回呼函數中進行顏色資料的擷取,請看下面代碼:
1 void onTimer( CAPTURE_INFO_INTERNAL & cii ) 2 { 3 BitBlt( 4 preview_info.comp_dc, 5 0, 6 0, 7 preview_info.width, 8 preview_info.height, 9 preview_info.target_dc,10 0,11 0,12 SRCCOPY );13 14 GetDIBits(15 preview_info.comp_dc,16 preview_info.comp_bmp,17 0,18 preview_info.height,19 preview_info.buf, 20 & preview_info.bmp_info,21 DIB_RGB_COLORS );22 }
沒錯,就是這麼簡單,只需要兩個 GDI 函數就可以擷取到案頭的顏色資料了,其中 cii 是一個 CAPTURE_INFO_INTERNAL 結構體,BitBlt( ) 函數負責將案頭 DC 的顏色資料傳送到相容 DC 裡面,然後 GetDIBits( ) 函數負責將顏色資料從相容 DC 裡面取出來,這樣子一次性進行資料轉送以及資料擷取,比起使用 GetPixel( ) 和 SetPixel( ) 快了不知道多少倍,利用這個技巧,甚至可以用來製作一些簡單的 2D 遊戲,在效能上差不多可以媲美 DirectDraw !!