中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何分析C++實現功能齊全的屏幕截圖示例

發布時間:2021-11-10 13:38:16 來源:億速云 閱讀:325 作者:柒染 欄目:開發技術

如何分析C++實現功能齊全的屏幕截圖示例,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。

    屏幕截圖已經成為了所有IM即時通訊軟件的必備模塊,也是日常辦公中使用最頻繁的功能之一。今天我們從C++開發的角度,來看看屏幕截圖的主要功能點是如何實現的,在此給大家分享一下屏幕截圖的諸多實現細節。

    開發工具:Visual Studio 2010

    開發語言:C++

    UI框架:MFC(也可以基于開源的duilib框架,其實在duilib中是調用Windows API)

    1、概述

    要使用屏幕截圖,其實很容易,裝一款聊天軟件或者辦公軟件就可以了,比如QQ、企業微信、釘釘、飛書等。但要開發出類似這些軟件的屏幕截圖模塊,則沒那么容易。其實實現屏幕截圖的技術并不復雜,主要是在各個細節問題的處理上。

    有人可能會說,我并不需要自己開發這些功能,我可以去搜一些開源的代碼,也可以到網上搜一堆關于屏幕截圖的文章或下載資源,應該可以找到能用的代碼或資源了。我想說的是,你大可以去試一試,很多都只是講到了一點皮毛,基本沒有一個實現了完備的截圖功能,沒有一個能拿到實際的項目中去使用的。簡單的寫幾句代碼,玩玩還可以,離真正商用到項目中,差的太遠了!真正項目級的代碼,要考慮各種場景和細節,要考慮性能和穩定性,是經過多輪測試錘煉出來的,不是隨便寫寫就能搞出來的!

    本文將結合開發屏幕截圖的實際項目經歷,詳細介紹一下屏幕截圖各個主要功能點的實現細節與方法,給大家提供一個借鑒和參考。

    2、屏幕截圖的主要功能點

    如何分析C++實現功能齊全的屏幕截圖示例

    一個具有完備功能的屏幕截圖應該包含以上多個功能點,比如桌面灰化、窗口自動套索、區域放大、矩形等多個圖元繪制、輸入文字等。

    3、屏幕截圖的主體實現思路

    網上很難找到一篇詳細介紹屏幕截圖完整功能的實現思路的,那屏幕截圖的主體實現思路到底是什么樣子的呢?下面我們就來簡單地描述一下。我們實現的一套屏幕截圖的效果如下(文章末尾處提供C++源碼下載):

    如何分析C++實現功能齊全的屏幕截圖示例

    下面基于我們實現的屏幕截圖,詳細介紹一下屏幕截圖主要的一些功能點和實現思路。

    3.1、截圖主窗口全屏置頂

    我們需要創建一個截圖的主窗口,開啟截圖后將該截圖主窗口全屏,覆蓋整個屏幕,并且給窗口設置TopMost置頂屬性。然后我們后續操作都是在這個全屏置頂的窗口上進行繪圖出來的,即截圖時截圖窗口中看到的所有內容(比如桌面灰化、窗口套索、區域放大、各個圖元等)都是繪制上去的!

    3.2、桌面灰化

    如何分析C++實現功能齊全的屏幕截圖示例

    在開啟截圖時,先將當前桌面上的圖像保存到位圖對象中,保存兩份位圖,一份是亮色的桌面圖像,一份是經過灰化后的桌面圖像。先將灰化的位圖繪制到截圖對話框上,實現灰化的遮罩。然后根據用戶拉動鼠標選擇的區域,從亮色位圖中摳出對應區域的亮色圖像繪制到對話框上,就能達到區域選擇的效果了。

    3.3、窗口自動套索

    如何分析C++實現功能齊全的屏幕截圖示例

    在啟動截圖時,需要遍歷當前系統中所有打開的窗口,以及這些窗口中的子窗口,把這些窗口的坐標位置記錄下來保存到內存中。當鼠標移動時,看鼠標移動到哪個最上層的窗口,然后在該窗口的區域繪制上套索的邊界,并將該窗口區域“亮”起來。亮起來其實很簡單,根據該窗口的坐標到內存中保存的亮色位圖中將對應的區域摳出來,繪制到窗口上,然后再在窗口邊界上繪制出套索邊界線即可。

    3.4、區域放大

    如何分析C++實現功能齊全的屏幕截圖示例

    其實實現這個功能并不難,可以仔細觀察以下主流IM軟件的顯示細節,就能找到思路和答案了!區域放大是實時地將鼠標移動到的位置的周圍區域放大,放大的區域是以鼠標點為中心的一小片矩形區域,然后將該區域放大4倍,將放大的效果繪制到截圖對話框上。

    3.5、截取區域的選擇

    如何分析C++實現功能齊全的屏幕截圖示例

    可以使用微軟MFC庫中提供的橡皮筋類CRectTracker來實現區域的選擇。該橡皮筋類對應一個選擇邊框,通過拉動鼠標,繪制出選擇區域的橡皮筋邊框,橡皮筋邊框支持拖動,改變橡皮筋邊框的大小。根據橡皮經選擇的區域,到內存中保存的亮色位圖中摳出亮色選擇區域,繪制到截圖窗口上就好了。

    3.5、截圖工具條

    截圖工具條一般做成一個緊貼截圖選擇區域的窗口,窗口中包含一排功能按鈕,一般包括矩形工具、橢圓工具、帶箭頭直線工具、曲線工具、Undo工具、關閉截圖、完成截圖這幾個功能按鈕。選擇矩形工具、橢圓工具、帶箭頭直線工具和曲線工具這四個按鈕后,鼠標在截圖窗口上繪制的就是對應類型的圖元。Undo按鈕是回撤上一次繪制的圖元。

    3.6、矩形等圖元的繪制

    如何分析C++實現功能齊全的屏幕截圖示例

    我們需要設計圖元類型對應的C++類,這些類統一繼承于一個CSharp的基類,基類中保存當前繪制圖元的線條顏色、起點和終點坐標,還有一個用于繪制圖元內容的純虛接口Draw,具體的Draw操作都在具體的圖元中實現。這其實使用C++中多態的概念。

    對于矩形、橢圓和帶箭頭的直線,我們只需要記錄圖元的起點和終點坐標就可以了,對于曲線,則由多個直線線段構成的,我們要記錄繪制過程中的多個點。當用戶左鍵按下時開始繪制圖元,記錄此時圖元起點坐標,在左鍵彈起時截圖當前圖元的繪制,記錄圖元的終點坐標,然后創建對應類型的圖元對象,將起點及終點坐標保存到對象中,然后把這些圖元對象保存到圖元列表中。窗口需要刷新時,調用列表中這些圖像的Draw接口將所有圖元繪制到截圖窗口上。

    4、桌面灰化的實現細節

    開啟截圖時,將桌面的圖像保存到亮色位圖對象中,同時對圖像進行灰化處理,將處理后的圖像保存到暗色位圖對象中。保存桌面圖像的代碼如下所示:

    // 拷貝桌面,lpRect 代表選定區域,bSave 標記是否將圖片內容保存到剪切板中
    HBITMAP CScreenCatchDlg::CopyScreenToBitmap( LPRECT lpRect ) 
    {                           
        // 確保選定區域不為空矩形
        if ( IsRectEmpty( lpRect ) )
        {
            return NULL;
        }
        
        CString strLog;
     
        // 為屏幕創建設備描述表
        HDC hScrDC = ::CreateDC( _T("DISPLAY"), NULL, NULL, NULL ); 
        if ( hScrDC == NULL )
        {
            strLog.Format( _T("[CCatchScreenDlg::CopyScreenToBitmap] 創建DISPLAY失敗, GetLastError: %d"), 
                GetLastError() );
            WriteScreenCatchLog( strLog );
     
            return NULL;
        }
     
        // 為屏幕設備描述表創建兼容的內存設備描述表
        HDC hMemDC = ::CreateCompatibleDC( hScrDC ); 
        if ( hMemDC == NULL )
        {
            strLog.Format( _T("[CCatchScreenDlg::CopyScreenToBitmap]創建與hScrDC兼容的hMemDC失敗, GetLastError: %d"), 
                GetLastError() );
            WriteScreenCatchLog( strLog );
     
            ::DeleteDC( hScrDC );
            return NULL;
        }
     
        int nX = 0;
        int nY = 0;
        int nX2 = 0;
        int nY2 = 0;   
        int nWidth = 0; 
        int nHeight = 0;
     
        // 保證left小于right,top小于bottom
        LONG lTemp = 0;
        if ( lpRect->left > lpRect->right )
        {
            lTemp = lpRect->left;
            lpRect->left = lpRect->right;
            lpRect->right = lTemp;
        }
        if ( lpRect->top > lpRect->bottom )
        {
            lTemp = lpRect->top;
            lpRect->top = lpRect->bottom;
            lpRect->bottom = lTemp;
        }
        
        // 獲得選定區域坐標
        nX = lpRect->left;
        nY = lpRect->top;
        nX2 = lpRect->right;
        nY2 = lpRect->bottom;
        
        // 確保選定區域是可見的
        if ( nX < 0 )
        {
            nX = 0;
        }
     
        if ( nY < 0 )
        {
            nY = 0;
        }
     
        if ( nX2 > m_xScreen )
        {
            nX2 = m_xScreen;
        }
     
        if ( nY2 > m_yScreen )
        {
            nY2 = m_yScreen;
        }
     
        nWidth = nX2 - nX;
        nHeight = nY2 - nY;
        // 創建一個與屏幕設備描述表兼容的位圖
        HBITMAP hBitmap = ::CreateCompatibleBitmap( hScrDC, nWidth, nHeight ); 
        if ( hBitmap == NULL )
        {
            strLog.Format( _T("[CCatchScreenDlg::CopyScreenToBitmap]創建與hScrDC兼容的Bitmap失敗, GetLastError: %d"), 
                GetLastError() );
            WriteScreenCatchLog( strLog );
     
            ::DeleteDC( hScrDC );
            ::DeleteDC( hMemDC );
            return NULL;
        }
        // 把新位圖選到內存設備描述表中
        ::SelectObject( hMemDC, hBitmap );     
        
        BOOL bRet = ::BitBlt( hMemDC, 0, 0, nWidth, nHeight, hScrDC, nX, nY, SRCCOPY | CAPTUREBLT );  // CAPTUREBLT - 該參數保證能夠截到透明窗口
        if ( !bRet )
        {
            strLog.Format( _T("[CCatchScreenDlg::CopyScreenToBitmap]將hScrDC拷貝到hMemDC失敗, GetLastError: %d"), 
                GetLastError() );
            WriteScreenCatchLog( strLog );
     
            ::DeleteDC( hScrDC );
            ::DeleteDC( hMemDC );
            ::DeleteObject( hBitmap );
            return NULL;
        }
        
        if ( hScrDC != NULL )
        {
            ::DeleteDC( hScrDC );
        }
     
        if ( hMemDC != NULL )
        {
            ::DeleteDC( hMemDC );
        }
     
        return hBitmap; // hBitmap資源不能釋放,因為函數外部要使用
    }

    如何將桌面圖像進行灰化處理呢?其實很簡單,只要將保存的桌面位圖中的每個像素值的RGB讀出來,將每個像素中的R、G、B值都乘以一個系數,然后再將這些值設置回位圖中即可,相關代碼如下:

    void CScreenCatchDlg::GrayLightBmp()
    {
        CString strLog;
     
        CDC *pDC = GetDC();
        ASSERT( pDC );
        if ( pDC == NULL )
        {
            strLog.Format( _T("[CCatchScreenDlg::DoGrayLightBmp] GetDC失敗, GetLastError: %d"), 
                GetLastError() );
            WriteScreenCatchLog( strLog );
            return;
        }
     
        CBitmap cbmp; 
        cbmp.Attach( m_hGreyBitmap ); // 此處使用臨時保存亮色位圖的m_hDarkBitmap
        BITMAP bmp; 
        cbmp.GetBitmap( &bmp ); 
        cbmp.Detach(); // 需要將對象和句柄分離,m_hDarkBitmap位圖資源需要保存在內存中,如不分離,則當對象消亡時,m_hDarkBitmap位圖資源會自動被釋放掉
        UINT *pData = new UINT[bmp.bmWidth * bmp.bmHeight]; 
        if ( pData == NULL )
        {
            int nSize = bmp.bmWidth * bmp.bmHeight;
            strLog.Format( _T("[CCatchScreenDlg::DoGrayLightBmp]pData通過new申請%s字節的內存失敗,直接return"), nSize );
            WriteScreenCatchLog( strLog );
     
            ReleaseDC( pDC );
            return;
        }
     
        BITMAPINFO bmpInfo; 
        bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); 
        bmpInfo.bmiHeader.biWidth = bmp.bmWidth; 
        bmpInfo.bmiHeader.biHeight = -bmp.bmHeight; 
        bmpInfo.bmiHeader.biPlanes = 1; 
        bmpInfo.bmiHeader.biCompression = BI_RGB; 
        bmpInfo.bmiHeader.biBitCount = 32; 
        
        int nRet = GetDIBits( pDC->m_hDC, m_hGreyBitmap, 0, bmp.bmHeight, pData, &bmpInfo, DIB_RGB_COLORS );
        if ( 0 == nRet )
        {
            strLog.Format( _T("[CCatchScreenDlg::DoGrayLightBmp]GetDIBits失敗 nRet == 0, GetLastError: %d"), 
                GetLastError() );
            WriteScreenCatchLog( strLog );
        }
     
        // 將圖像中的所有像素點的RGB值都乘以0.4,即實現了圖像的灰化
        UINT color, r, g, b; 
        for ( int i = 0; i < bmp.bmWidth * bmp.bmHeight; i++ ) 
        { 
            color = pData[i]; 
            b = ( color << 8 >> 24 ) * 0.4; 
            g = ( color << 16 >> 24 ) * 0.4; 
            r = ( color << 24 >> 24 ) * 0.4; 
            pData[i] = RGB(r, g, b); 
        } 
     
        // 如果函數成功,那么返回值就是復制的掃描線數;如果函數失敗,那么返回值是0。
        nRet = SetDIBits( pDC->m_hDC, m_hGreyBitmap, 0, bmp.bmHeight, pData, &bmpInfo, DIB_RGB_COLORS ); 
        if ( 0 == nRet )
        {
            strLog.Format( _T("[CCatchScreenDlg::DoGrayLightBmp]SetDIBits失敗 nRet == 0, GetLastError: %d"), 
                GetLastError() );
            WriteScreenCatchLog( strLog );
        }
     
        delete []pData;
        pData = NULL;
        ReleaseDC( pDC );
    }

    內存中要保留兩份位圖,一份是亮色的桌面圖像,一份是經過灰化后的桌面圖像。先將灰化的位圖繪制到截圖對話框上,實現灰化的遮罩。然后根據用戶拉動鼠標選擇的區域,從亮色位圖中摳出對應區域的亮色圖像繪制到對話框上,就能達到區域選擇的效果了。

    5、窗口自動套索實現

    在啟動截圖時,需要遍歷當前系統中所有打開的窗口,以及這些窗口中的子窗口,把這些窗口的坐標位置記錄下來保存到內存中。先調用系統API函數EnumWindows,將系統中打開的窗口都枚舉出來:

    	// 使用EnumWindows來枚舉當前系統打開的所有大窗口
    	::EnumWindows( EnumWindowsProc, NULL );
     
     
    BOOL CEnumWindows::EnumWindowsProc( HWND hWnd, LPARAM lParam )
    {
    	TCHAR achWndName[MAX_PATH+1] = {0};
     
    	if ( ::IsWindow(hWnd) && ::IsWindowVisible(hWnd) && !::IsIconic(hWnd) )
    	{
    		// 保存所有有效窗口
    		EnumedWindowInfo tWndInfo;
    		tWndInfo.m_hWnd = hWnd;
     
    		::GetWindowText( hWnd, achWndName, sizeof(achWndName)/sizeof(TCHAR) );
    		tWndInfo.m_strWndName = achWndName;
     
    		 將桌面區域過濾掉
    		//if ( !_tcscmp( tWndInfo.m_strWndName, _T("Program Manager") ) )
    		//{
    		//	return TRUE;
    		//}
     
    		::GetWindowRect( hWnd, &(tWndInfo.m_rcWnd) );
    		m_listWindows.push_back( tWndInfo );
    	}
     
    	return TRUE;
    }

    然后再遍歷這些窗口,使用遞歸調用的方式找出這些主窗口的各個子窗口,記錄下這些子窗口的信息。

    當鼠標移動時,根據鼠標的位置坐標,到窗口信息列表中去遍歷,看鼠標移動到哪個最上層的窗口,然后在該窗口的區域繪制上套索的邊界,并將該窗口區域“亮”起來。亮起來其實很簡單,根據該窗口的坐標到內存中保存的亮色位圖中將對應的區域摳出來,繪制到窗口上,然后再在窗口邊界上繪制出套索邊界線即可。

    6、區域放大實現

    實現這點也不難,可以仔細觀察以下主流IM軟件的顯示細節,就能找到思路與方法了!區域放大是實時地將鼠標移動到的位置的周圍區域放大,放大的區域是以鼠標點為中心的一小片矩形區域。

    確定待放大區域的坐標后,從內存中保存的桌面亮色位圖中摳出亮色的待放大區域,然后調用StretchBlt將放大后的圖像繪制到截圖窗口上,相關代碼如下:

    // 在內存pMemDC中繪制自動套索窗口
    void CScreenCatchDlg::DrawAutoLassoWndArea( CDC* pMemDC, CDC* pLightDC )
    {
    	if ( pMemDC == NULL || pLightDC == NULL )
    	{
    		return;
    	}
     
    	if ( m_rcTargetWnd.IsRectEmpty() )
    	{
    		return;
    	}
     
    	// 先從亮色圖片將目標窗口摳出
    	CRect rcArea = m_rcTargetWnd;
    	BOOL bRet = pMemDC->BitBlt( rcArea.left, rcArea.top, rcArea.Width(), rcArea.Height(),
    		pLightDC, rcArea.left, rcArea.top, SRCCOPY );
    	if ( !bRet )
    	{
    		WriteScreenCatchLog( _T("[CCatchScreenDlg::DrawAutoLassoWndPic]pMemDC->BitBlt(rcArea.left,rcArea.top…失敗") );
    	}
     
    	rcArea.left = (rcArea.left-4<0) ? 4 : rcArea.left;
    	rcArea.top = (rcArea.top-4<0) ? 4 : rcArea.top;
    	rcArea.right = (rcArea.right+4>m_xScreen) ? (m_xScreen-4) : rcArea.right;
    	rcArea.bottom = (rcArea.bottom+4>m_yScreen) ? (m_yScreen-4) : rcArea.bottom;
     
    	// 再在目標窗口周邊畫上自動套索邊界線
        CPen pen( PS_SOLID, 1, RGB( 0, 174, 255 ) );
    	CPen* pOldPen = pMemDC->SelectObject( &pen );
    	CBrush* pOldBrush = ( CBrush* )pMemDC->SelectStockObject( NULL_BRUSH ); // 使用NULL_BRUSH調用SelectStockObject可以實現透明畫刷的效果
    	rcArea.InflateRect( 1, 1 );
    	pMemDC->Rectangle( &rcArea );
    	rcArea.InflateRect( 1, 1 );
    	pMemDC->Rectangle( &rcArea );
    	rcArea.InflateRect( 1, 1 );
    	pMemDC->Rectangle( &rcArea );
    	rcArea.InflateRect( 1, 1 );
    	pMemDC->Rectangle( &rcArea );
    	//rcArea.DeflateRect( 1, 1 );
    	//rcArea.DeflateRect( 1, 1 );
    	//pMemDC->Rectangle( &rcArea );
    	//rcArea.DeflateRect( 1, 1 );
    	//pMemDC->Rectangle( &rcArea );
    	//rcArea.DeflateRect( 1, 1 );
    	//pMemDC->Rectangle( &rcArea );
     
    	pMemDC->SelectObject( pOldBrush );
    	pMemDC->SelectObject( pOldPen );
    }

    7、截取區域的選擇

    微軟MFC庫中的橡皮筋類CRectTracker是個好東西,繪制出來的是個有邊框線的矩形邊界線,邊框線上有八個點可以用鼠標點擊拖動來改變矩形邊界線的大小。我們正可以使用這個橡皮筋類來實現截圖區域的選擇。

    橡皮筋類CRectTracker實現的有點復雜,也很巧妙,我們將該類的代碼從MFC庫中拿出來,對其進行一些簡單靈活的改造,就可以用到截圖模塊中。添加一些消息通知和額外的處理機制。摳出來的類,我們命名為CCatchTracker,其頭文件如下所示:

    /
    // CCatchTracker - simple rectangular tracking rectangle w/resize handles
     
     
    // CCatchTracker類從MFC源文件COPY過來,根據自身的需要做了修改,對消息機制
    // 做了點改動,增加了部分接口
     
     
    #ifndef CATCH_SCREEN_TRACKER_H
    #define CATCH_SCREEN_TRACKER_H
     
    #define CX_BORDER   1
    #define CY_BORDER   1
     
    #define WM_UPDATE_TOOLBAR_POS ( WM_USER+700 ) // 更新截圖工具條位置消息,當截取區域發生變化時要向界面發送該消息
     
    #define CRIT_RECTTRACKER    5
    void AFXAPI AfxLockGlobals(int nLockType);
    void AFXAPI AfxUnlockGlobals(int nLockType);
    void AFXAPI AfxDeleteObject(HGDIOBJ* pObject);
     
    enum TrackerHit
    {
    	hitNothing = -1,
    	hitTopLeft = 0, hitTopRight = 1, hitBottomRight = 2, hitBottomLeft = 3,
    	hitTop = 4, hitRight = 5, hitBottom = 6, hitLeft = 7, hitMiddle = 8
    };
     
    class CCatchTracker
    {
    public:
    // Constructors
    	CCatchTracker();
    	CCatchTracker(LPCRECT lpSrcRect, UINT nStyle);
     
    // Style Flags
    	enum StyleFlags
    	{
    		solidLine = 1, dottedLine = 2, hatchedBorder = 4,
    		resizeInside = 8, resizeOutside = 16, hatchInside = 32,
    		resizeMiddle =80 //設置中間
    	};
     
    // Hit-Test codes
    	//enum TrackerHit
    	//{
    	//	hitNothing = -1,
    	//	hitTopLeft = 0, hitTopRight = 1, hitBottomRight = 2, hitBottomLeft = 3,
    	//	hitTop = 4, hitRight = 5, hitBottom = 6, hitLeft = 7, hitMiddle = 8
    	//};
     
    // Operations
    	void Draw(CDC* pDC) const;
    	void GetTrueRect(LPRECT lpTrueRect) const;
    	BOOL SetCursor(CWnd* pWnd, UINT nHitTest) const;
    	BOOL Track(CWnd* pWnd, CPoint point, BOOL bAllowInvert =TRUE,
    		CWnd* pWndClipTo = NULL);
    	BOOL TrackRubberBand(CWnd* pWnd, CPoint point, BOOL bAllowInvert = TRUE);
    	int HitTest(CPoint point) const;
    	int NormalizeHit(int nHandle) const;
     
    // Overridables
    	virtual void DrawTrackerRect(LPCRECT lpRect, CWnd* pWndClipTo,
    		CDC* pDC, CWnd* pWnd);
    	virtual void AdjustRect(int nHandle, LPRECT lpRect);
    	virtual void OnChangedRect(const CRect& rectOld);
    	virtual UINT GetHandleMask() const;
     
    // Implementation
    public:
    	virtual ~CCatchTracker();
     
    public:
    	// 設置調整光標
    	void SetResizeCursor(UINT nID_N_S,UINT nID_W_E,UINT nID_NW_SE, UINT nID_NE_SW,UINT nIDMiddle);
    	// 創建畫刷,內部調用
    	void CreatePen();
    	// 設置矩形顏色
    	void SetRectColor(COLORREF rectColor);
    	// 設置該矩形tracker是否可以移動,當點擊截圖工具條中的按鈕后即不可移動
    	void SetMovable( BOOL bMoveable );
    	BOOL GetMovable(){ return m_bMovable; };
     
    	// implementation helpers
    	int HitTestHandles(CPoint point) const;
    	void GetHandleRect(int nHandle, CRect* pHandleRect) const;
    	void GetModifyPointers(int nHandle, int**ppx, int**ppy, int* px, int*py);
    	virtual int GetHandleSize(LPCRECT lpRect = NULL) const;
    	BOOL TrackHandle(int nHandle, CWnd* pWnd, CPoint point, CWnd* pWndClipTo);
    	void Construct();
        void SetMsgHwnd(HWND hwnd);
     
    public:
    	// Attributes
    	UINT m_nStyle;          // current state
    	CRect m_rect;           // current position (always in pixels)
    	CSize m_sizeMin;        // minimum X and Y size during track operation
    	int m_nHandleSize;      // size of resize handles (default from WIN.INI)
    	BOOL m_bAllowInvert;    // flag passed to Track or TrackRubberBand
    	CRect m_rectLast;
    	CSize m_sizeLast;
    	BOOL m_bErase;          // TRUE if DrawTrackerRect is called for erasing
    	BOOL m_bFinalErase;     // TRUE if DragTrackerRect called for final erase
     
    	COLORREF m_rectColor;   // 當前矩形顏色
        HWND m_hMsgWnd;         // 向界面發送消息的窗口句柄
        BOOL m_bMovable;        // 標記該矩形tracker是否可以移動,當點擊截圖工具條中的按鈕后即不可移動
    };
     
    #endif

    根據橡皮經選擇的區域,到內存中保存的亮色位圖中摳出亮色選擇區域,繪制到截圖窗口上就好了。截圖工具條是緊貼著橡皮筋選擇區域的,位于該區域的下方,當橡皮筋區域大小發生變化時,要通知截圖工具條窗口跟著截圖區域一起動,使截圖工具條緊跟著橡皮筋選擇區域。所以我們在橡皮筋類中拋出如下的通知消息:

    		switch (msg.message)
    		{
    		// handle movement/accept messages
    		case WM_LBUTTONUP:
    		case WM_MOUSEMOVE:
    			rectOld = m_rect;
    			// handle resize cases (and part of move)
    			if (px != NULL)
    				*px = (int)(short)LOWORD(msg.lParam) - xDiff;
    			if (py != NULL)
    				*py = (int)(short)HIWORD(msg.lParam) - yDiff;
     
    			// handle move case
    			if (nHandle == hitMiddle)
    			{
    				m_rect.right = m_rect.left + nWidth;
    				m_rect.bottom = m_rect.top + nHeight;
    			}
     
    			// 發送矩形區域的左上角和右下角的坐標給界面,一方面在移動矩形時要用到,
    			// 一方面在更新界面中的截圖工具條的位置時要用到
    			if ( IsWindow( m_hMsgWnd ) ) // 檢驗是否是有效的窗口句柄
    			{
    				BOOL bLBtnUp = FALSE;
    				if ( msg.message == WM_LBUTTONUP )
    				{
    					bLBtnUp = TRUE;
    				}
    				::SendMessage(m_hMsgWnd, WM_UPDATE_TOOLBAR_POS, (WPARAM)&m_rect, (LPARAM)bLBtnUp );
    			}

    8、矩形等圖元的繪制

    截圖中要支持矩形、橢圓、帶箭頭直線和曲線四種圖元的繪制,我們分別設計了與圖元類型對應的C++類,這些類統一繼承于一個CSharp的基類,基類中保存當前繪制圖元的線條顏色、起點和終點坐標,還有一個用于繪制圖元內容的純虛接口Draw:

    // 形狀基類
    class CShape
    {
    public:
    	CShape();
    	virtual ~CShape();
     
    	virtual void Draw( CDC* pDC ) = 0;
     
    protected:
    	CPoint m_startPt;  // 起點
    	CPoint m_endPt;    // 終點
        COLORREF m_color;  // 當前使用顏色
    };

    具體的Draw操作都在具體的圖元中實現。這其實使用C++中多態的概念。

    以矩形圖元為例,矩形類CRectangle的頭文件如下:

    // 矩形
    class CRectangle : public CShape
    {
    public:
    	CRectangle( CPoint startPt, CPoint endPt );
    	~CRectangle();
     
    	void Draw( CDC* pDC );
    };

    cpp源文件的代碼如下:

    CRectangle::CRectangle( CPoint startPt, CPoint endPt )
    {
    	m_startPt = startPt;
    	m_endPt = endPt;
    }
     
    CRectangle::~CRectangle()
    {
     
    }
     
    void CRectangle::Draw( CDC* pDC )
    {
    	if ( pDC == NULL )
    	{
    		return;
    	}
     
    	Pen pen( Color(255, 0, 0), 2.0 );
    	pen.SetLineCap(LineCapRound, LineCapRound, DashCapRound);
    	Graphics graphics( pDC->GetSafeHdc() );
    	//graphics.SetSmoothingMode( SmoothingModeAntiAlias );
    	//graphics.DrawRectangle( &pen, m_startPt.x, m_startPt.y, m_endPt.x-m_startPt.x, m_endPt.y-m_startPt.y );
     
    	CRect rcTemp(  m_startPt.x, m_startPt.y, m_endPt.x, m_endPt.y );
    	rcTemp.NormalizeRect();
    	Status stRet = graphics.DrawRectangle( &pen, rcTemp.left, rcTemp.top, 
    		rcTemp.Width(), rcTemp.Height() );
    }

    對于矩形、橢圓和帶箭頭的直線,我們只需要記錄圖元的起點和終點坐標就可以了,對于曲線,則由多個直線線段構成的,我們要記錄繪制過程中的多個點。當用戶左鍵按下時開始繪制圖元,記錄此時圖元起點坐標,在左鍵彈起時截圖當前圖元的繪制,記錄圖元的終點坐標,然后創建對應類型的圖元對象,將起點及終點坐標保存到對象中,然后把這些圖元對象保存到圖元列表中。窗口需要刷新時,調用列表中這些圖像的Draw接口將所有圖元繪制到截圖窗口上。

    最開始我們是使用GDI函數繪制圖元的,比如GDI中的API函數Reactangle(繪制矩形)、Ellipse(繪制橢圓)等,但在繪制帶箭頭的直線和曲線時,GDI函數繪制出來的結果中有明顯的鋸齒,效果很不好。所以后來我們將圖元的繪制全部改成使用GDI+庫來處理,GDI+中的Graphics類在繪制圖元時,可以設置反鋸齒的模式:

    	case emBtnEllipse: // 畫橢圓
    		{
    			// 為了抗鋸齒,均使用GDI+來繪制圖元(GDI繪制直線和曲線時有明顯的鋸齒)
    			Pen pen( Color(255, 0, 0), WIDTH_DRAW_PEN );
    			Graphics graphics( m_tmpDrawDC.GetSafeHdc() );
    			graphics.SetSmoothingMode( SmoothingModeAntiAlias );
    			graphics.DrawEllipse( &pen, m_drawStartPt.x/*-m_rectTracker.m_rect.left*/, m_drawStartPt.y/*-m_rectTracker.m_rect.top*/, 
    				point.x-m_drawStartPt.x/*+m_rectTracker.m_rect.left*/, 
    				point.y-m_drawStartPt.y/*+m_rectTracker.m_rect.top*/ );
    		}
    		break;

    9、截圖窗口的繪制機制

    整個全屏置頂的截圖主窗口上面顯示的所有內容都是都是我們在截圖窗口中繪制出來的,比如窗口的自動套索效果、區域放大效果、截圖區域的橡皮筋選擇框、各種圖元的繪制等。

    我們要在截圖窗口上接管所有內容的繪制,需要攔截截圖窗口的WM_ERASEBKGND和WM_PAINT消息。首先在收到WM_ERASEBKGND消息后,直接return TRUE,不需要系統幫我們繪制背景:

    BOOL CScreenCatchDlg::OnEraseBkgnd( CDC* pDC ) 
    {
    	return TRUE;
    }

    在收到WM_PAINT消息時,使用雙緩沖繪制去繪制截圖窗口上要繪制的內容。所謂雙緩沖繪圖的思想是,先將所有需要繪制的內容繪制到內存DC上,這些繪制可能需要時間,然后再將內存DC中的內容繪制到窗口(DC)上。雙緩沖繪圖是解決繪制時窗口閃爍的有效方法。

    在處理WM_PAINT消息時,需要調用BeginPaint和EndPaint在繪制完窗口后將窗口的無效區域清空,切記要記得調用這兩個函數。如果不調用這兩個接口,會導致窗口一直有無效區域,這樣系統一直都檢測到窗口有無效區域,一直在不斷地產生WM_PAINT消息,這樣程序一直在忙于處理WM_PAINT消息,導致低優先的WM_TIMER消息被淹沒被丟棄,界面由于在不斷繪制會產生嚴重的閃爍問題。在我們的OnPaint函數中,我們使用到了CPaintDC類,該類中封裝了對BeginPaint和EndPaint的調用:

    CPaintDC::CPaintDC(CWnd* pWnd)
    {
    	ASSERT_VALID(pWnd);
    	ASSERT(::IsWindow(pWnd->m_hWnd));
     
    	if (!Attach(::BeginPaint(m_hWnd = pWnd->m_hWnd, &m_ps)))
    		AfxThrowResourceException();
    }
     
    CPaintDC::~CPaintDC()
    {
    	ASSERT(m_hDC != NULL);
    	ASSERT(::IsWindow(m_hWnd));
     
    	::EndPaint(m_hWnd, &m_ps);
    	Detach();
    }

    有時我們在某些操作后,我們想讓窗口立即刷新,可以組合調用InvalidateRect和UpdateWindow,InvalidateRect是讓窗口無效,UpdateWindow是讓系統立即產生WM_PAINT消息,并將WM_PAINT投遞到窗口過程(不是將WM_PAINT放到消息隊列中等待處理),這樣窗口能立即刷新。調用UpdateWindow就相當于讓窗口立即強制刷新。
    至于WM_PAINT、BeginPaint、InvalidateRect和UpdateWindow之間的關系,可以參見我之前專門寫的一篇主題文章:https://blog.csdn.net/chenlycly/article/details/120931704,里面有詳細地講述這些對象的關系。

    10、截圖退出類型的詳細設計

    有多種退出截圖的場景,不同的退出場景可能需要有不同的后續處理,所以我們定義了多種退出截圖時的類型:

    enum EmQuitType
    {
    	emQuitInvalid = -1,     // 無效退出類型
    	emESCQuit   = 0,        // 按ESC鍵退出
    	emRClickQuit,           // 右鍵單擊退出
    	emLDClickQuit,          // 左鍵雙擊退出
    	emSendtoBlogQuit,       // 發送到微博退出
    	emSaveQuit,             // 保存截圖后退出
    	emCancelQuit,           // 取消截圖退出
    	emCompleteQuit,         // 完成截圖退出
    	emMemoryLackQuit,       // 內存不足引起的gdi操作失敗退出
    	emCutRectEmptyQuit      // 截取區域為空退出       
    };

    1)按下ESC鍵退出、右鍵點擊退出、保存圖片退出、點擊取消按鈕退出、截取區域為空退出
    這些場景下退出截圖,截圖模塊不需要任何處理,都是單純的退出截圖。

    2)雙擊截圖區域退出截圖、點擊完成按鈕退出截圖
    這些場景下,在退出截圖之前,會將截取區域的圖片位圖保存到剪切板中,同時將截圖保存到磁盤文件中。退出截圖后,如果是聊天框中的截圖入口觸發的,需要將截取的圖片自動插入到聊天框中。

    3)內存不足截圖失敗退出

    這種場景是因為系統內存不足導致GDI函數調用失敗,外部需要彈出“截圖失敗,可能是系統內存不足引起的,退出部分程序后再試”的提示。
    所以我們根據這些退出的場景設計了對應的退出類型,在退出截圖時設置退出類型,并提供獲取退出截圖時退出類型的接口GetQuitType,這樣在退出截圖后,外部調用GetQuitType獲取當前截圖退出的類型,看是否需要進行后續的處理。

    11、創建位圖時將CreateCompatibleBitmap替換成CreateDIBSection

    最開始我們再代碼中創建位圖時調用的是CreateCompatibleBitmap,但是該接口在系統內存不是很充足的時候會經常返回失敗,在日常的測試中經常遇到。通過GetLastError獲取到CreateCompatibleBitmap調用失敗后的錯誤碼是8:

    如何分析C++實現功能齊全的屏幕截圖示例

    該錯誤碼的描述如上,意思就是當前系統的可用內存空間不多了,而調用CreateCompatibleBitmap創建位圖時需要申請一定的內存空間,空間不夠時該函數就會返回失敗了。

    經后來查閱相關資料得知,袁峰老師在他編寫的《Windows圖形編程》一書中提過,CreateCompatibleBitmap創建的文圖是DDB位圖,是依賴設備的設備相關位圖,是從內核地址空間中分配的,而內核內存資源比較有限,建議使用CreateDIBSection來創建位圖,書中的具體描述如下:

    如何分析C++實現功能齊全的屏幕截圖示例

    CreateDIBSection創建的位圖是DIB位圖,是不依賴于設備的設備無關位圖,是從用戶態地址空間中的虛擬內存中分配的,限制比較少,一般都會成功。所以后來我們封裝了一個創建位圖的接口,如下:

    // 創建設備無關位圖,解決調用CreateCompatibleBitmap API函數因內存不足創建位圖
    // 失敗的問題
    HBITMAP CreateDIBBitmap( const int nWidth, const int nHeight )
    {
    	BITMAPINFO bmi;
    	::ZeroMemory( &bmi, sizeof(bmi) );
    	bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
    	bmi.bmiHeader.biWidth = nWidth;
    	bmi.bmiHeader.biHeight = nHeight;
    	bmi.bmiHeader.biPlanes = 1;
    	bmi.bmiHeader.biBitCount = 32;
    	bmi.bmiHeader.biCompression = BI_RGB;
    	bmi.bmiHeader.biSizeImage = nWidth * nHeight * 4;//4=bmi.bmiHeader.biBitCount/8
     
    	void* pvBits = NULL;
    	return ::CreateDIBSection( NULL, &bmi, DIB_RGB_COLORS, &pvBits, NULL, 0 );
    }

    12、最后

    本文講述了屏幕截圖中的一些實現思路與細節,但在實際實現時的細節比上面說的多的多!

    此處,我們提供一個工程級、高質量的完整屏幕截圖的C++實現源碼下載鏈接:ScreenCatch.zip

    在源碼中,我們將截圖模塊封裝成一個dll,并提供了一個調用dll接口的工程TestScreenCatch(該工程和截圖dll均提供完整的C++源碼),調用截圖dll接口的代碼如下:

    void CTestScreenCatchDlg::OnBnClickedBtnStartCapture()
    {
    	CString strPath = GetModuleFullPath();
     
    	// 該接口中會彈出截圖的模態框,截圖對話框關閉后該接口才會返回
    	// 接口彈出模塊框,不會堵塞整個線程,模態框內部會接管消息循環,會分發消息
    	DoScreenCatch( (LPCTSTR)strPath );
     
    	EmQuitType emQuitType = GetQuitType();
    	if ( emQuitType == emLDClickQuit || emQuitType == emCompleteQuit )
    	{
    		if ( IsPicFileSaved() )
    		{
    			TCHAR achPciPath[MAX_PATH] = { 0 };
    			GetPicFileSavedPath( achPciPath, sizeof(achPciPath)/sizeof(TCHAR) );
     
    			CString strTip;
    			strTip.Format( _T("截圖保存到路徑:%s"), achPciPath );
    			AfxMessageBox( strTip );
    		}
    	}
    	else if ( emQuitType == emMemoryLackQuit )
    	{
    		AfxMessageBox( _T("截圖失敗,可能是內存不足引起的,退出部分程序后再試!") );
    	}
    }

    看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    c++
    AI

    云龙县| 金秀| 梁河县| 台前县| 濮阳市| 保靖县| 博乐市| 库车县| 清新县| 安宁市| 肥东县| 阿拉善盟| 阿拉善左旗| 尼勒克县| 建阳市| 新野县| 平凉市| 浮梁县| 镇原县| 花莲市| 恭城| 灵山县| 韩城市| 虹口区| 铁岭县| 潞城市| 凤台县| 苏尼特右旗| 卓资县| 额敏县| 新邵县| 乐平市| 昌江| 平定县| 澳门| 斗六市| 思茅市| 大悟县| 苗栗市| 儋州市| 马关县|