截全屏時如何過濾部分窗口

  • 2019 年 11 月 24 日
  • 筆記

系統學習Windows客戶端開發

在某些業務場景下希望截全屏時不顯示某些窗口特別是自身應用的窗口,比如在屏幕共享時不希望將自己應用的主界面、工具條等共享給對方。

Windows有個特性Magnification(放大鏡)特性,它允許將屏幕(或屏幕某個指定區域)進行放大,如果不設置放大比例等同於截屏,其支持選擇窗口過濾,利用該特性就可以實現過濾部分窗口下截屏。該特性從Vista開始支持,如果產品需要支持Win XP系統就不能使用該方案。

使用Magnification進行截屏的流程如下:

筆者編寫類CScreenCapture,用來實現過濾部分窗口截圖,結合MSDN仔細閱讀理解就容易掌握其使用。CScreenCapture類提供三個接口SetFilterWindowList()指定過濾窗口列表,SetFrameRate()指定每秒幀數,SetScreenImageArriveCallback()設置回調接收圖片。內部開啟一個UI線程定期執行截屏,線程創建運行使用 一個簡單實用的線程基類CThreadBase,最後DEMO演示如何在接收圖片回調中將其保存成BITMAP格式的圖片。

類CScreenCapture頭文件

#pragma once  #include <Magnification.h>  #include <vector>  #include <map>  #include "ThreadBase.h"    class IScreenImageArriveCallback  {  public:      // called when screen was captured      // param is same as MagImageScalingCallback      virtual void OnScreenImageArriveCallback(MAGIMAGEHEADER srcheader, void *srcdata) = 0;  };    class CScreenCapturer : public CThreadBase  {  public:      CScreenCapturer();      ~CScreenCapturer();    public:      // set the callback of screen image arrived      void SetScreenImageArriveCallback(IScreenImageArriveCallback* pCallback);        // set filter window list that will not show on the screen image      void SetFilterWindowList(const std::vector<HWND>& vecFilterWindow);        // frame rate represent times of capturing screen per second      bool SetFrameRate(unsigned int nFrameRate);    protected:  // override CThreadBase      virtual bool OnStart(const std::string& strParam) override;      virtual void OnRun(const std::string& strParam) override;      virtual void OnStop() override;    private:      static BOOL MagImageScaling(HWND hwnd, void *srcdata, MAGIMAGEHEADER srcheader, void *destdata, MAGIMAGEHEADER destheader, RECT unclipped, RECT clipped, HRGN dirty);      static LRESULT CALLBACK MsgWndWindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);      void CaptureScreen();    private:      CRITICAL_SECTION m_syncObject;      DWORD m_nThreadId = 0;      HWND m_hwndMagnification = NULL;      RECT m_rectScreen;      IScreenImageArriveCallback* m_pScreenImageArriveCallback = nullptr;      HWND* m_pFilterWindows = nullptr;      unsigned int m_nFilterWindowsCount = 0;      unsigned int m_nFrameRate = 15;  };

類CScreenCapture源文件

#include "ScreenCapturer.h"    #pragma comment(lib, "Magnification.lib")    #define WINDOW_CLASS_NAME  L"magnification_host"  #define TIMER_ID_CAPTURE_SCREEN     100    CScreenCapturer::CScreenCapturer()  {      InitializeCriticalSection(&m_syncObject);  }    CScreenCapturer::~CScreenCapturer()  {      if (m_pFilterWindows)      {          delete[] m_pFilterWindows;          m_pFilterWindows = nullptr;      }        DeleteCriticalSection(&m_syncObject);  }    void CScreenCapturer::SetScreenImageArriveCallback(IScreenImageArriveCallback* pCallback)  {      m_pScreenImageArriveCallback = pCallback;  }    void CScreenCapturer::SetFilterWindowList(const std::vector<HWND>& vecFilterWindow)  {      EnterCriticalSection(&m_syncObject);      m_nFilterWindowsCount = vecFilterWindow.size();      if (m_pFilterWindows)      {          delete[] m_pFilterWindows;          m_pFilterWindows = nullptr;      }      if (m_nFilterWindowsCount > 0)      {          m_pFilterWindows = new HWND[m_nFilterWindowsCount];          for (unsigned int i = 0; i < m_nFilterWindowsCount; i++)          {              m_pFilterWindows[i] = vecFilterWindow[i];          }      }      LeaveCriticalSection(&m_syncObject);  }    bool CScreenCapturer::SetFrameRate(unsigned int nFrameRate)  {      if (nFrameRate == 0 || nFrameRate > 60)      {          return false;      }        m_nFrameRate = nFrameRate;      return true;  }    bool CScreenCapturer::OnStart(const std::string& strParam)  {      return true;  }    void CScreenCapturer::OnRun(const std::string& strParam)  {      m_nThreadId = GetCurrentThreadId();        // Init magnification      if (!MagInitialize())      {          return;      }        // Register magnification host window class      WNDCLASS wc = { 0 };      wc.lpszClassName = WINDOW_CLASS_NAME;      wc.hbrBackground = CreateSolidBrush(RGB(255, 255, 255));      wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);      wc.hCursor = LoadCursor(NULL, IDC_ARROW);      wc.lpfnWndProc = MsgWndWindowProc;      wc.hInstance = NULL;      if (RegisterClass(&wc) == 0 && GetLastError() != ERROR_CLASS_ALREADY_EXISTS)      {          return;      }        // Get screen resolution      HWND hDesktop = ::GetDesktopWindow();      ::GetWindowRect(hDesktop, &m_rectScreen);      int nScreenX = m_rectScreen.right - m_rectScreen.left;      int nScreenY = m_rectScreen.bottom - m_rectScreen.top;        // Create host window      HWND hHostWindow = CreateWindowEx(WS_EX_LAYERED, WINDOW_CLASS_NAME, L"", WS_POPUP, 0, 0, nScreenX, nScreenY, GetDesktopWindow(), 0, NULL, 0);      if (hHostWindow == NULL)      {          return;      }      SetLayeredWindowAttributes(hHostWindow, 0, 255, LWA_ALPHA);      SetWindowLong(hHostWindow, GWL_USERDATA, (LONG_PTR)this);        // Create magnification window      m_hwndMagnification = CreateWindow(WC_MAGNIFIER, L"MagnifierWindow", WS_CHILD | WS_VISIBLE, 0, 0, nScreenX, nScreenY, hHostWindow, NULL, NULL, NULL);      if (m_hwndMagnification == NULL)      {          DestroyWindow(hHostWindow);          return;      }        // Set capture callback      if (!MagSetImageScalingCallback(m_hwndMagnification, (MagImageScalingCallback)&CScreenCapturer::MagImageScaling))      {          DestroyWindow(hHostWindow);          return;      }        // Set timer to capture screen      SetTimer(hHostWindow, TIMER_ID_CAPTURE_SCREEN, 1000/m_nFrameRate, nullptr);        MSG msg;      while (GetMessage(&msg, 0, 0, 0))      {          DispatchMessage(&msg);      }        KillTimer(hHostWindow, TIMER_ID_CAPTURE_SCREEN);      DestroyWindow(hHostWindow);      m_hwndMagnification = NULL;  }    void CScreenCapturer::OnStop()  {      if (m_nThreadId > 0)      {          PostThreadMessage(m_nThreadId, WM_QUIT, 0, 0);          m_nThreadId = 0;      }  }    BOOL CScreenCapturer::MagImageScaling(HWND hwnd, void *srcdata, MAGIMAGEHEADER srcheader, void *destdata, MAGIMAGEHEADER destheader, RECT unclipped, RECT clipped, HRGN dirty)  {      HWND hParent = hwnd;      while (true)      {          hParent = GetParent(hParent);          if (hParent == NULL)          {              break;          }            wchar_t szClassName[200];          memset(szClassName, 0, sizeof(szClassName));          GetClassName(hParent, szClassName, 200);          if (wcscmp(szClassName, WINDOW_CLASS_NAME) == 0)          {              break;          }      }      if (hParent == NULL)      {          return TRUE;      }        CScreenCapturer* pScreenCapture = (CScreenCapturer*)GetWindowLong(hParent, GWL_USERDATA);      if (pScreenCapture && pScreenCapture->m_pScreenImageArriveCallback)      {          pScreenCapture->m_pScreenImageArriveCallback->OnScreenImageArriveCallback(srcheader, srcdata);      }        return TRUE;  }    LRESULT CALLBACK CScreenCapturer::MsgWndWindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)  {      if (message == WM_TIMER && wParam == TIMER_ID_CAPTURE_SCREEN)      {          CScreenCapturer* pScreenCapture = (CScreenCapturer*)GetWindowLong(hWnd, GWL_USERDATA);          if (pScreenCapture)          {              pScreenCapture->CaptureScreen();          }            return 0L;      }        return DefWindowProc(hWnd, message, wParam, lParam);  }    void CScreenCapturer::CaptureScreen()  {      EnterCriticalSection(&m_syncObject);      MagSetWindowFilterList(m_hwndMagnification, MW_FILTERMODE_EXCLUDE, m_nFilterWindowsCount, m_pFilterWindows);      LeaveCriticalSection(&m_syncObject);        MagSetWindowSource(m_hwndMagnification, m_rectScreen);  }

接收圖片回調中保存成BITMAP格式的圖片

void CMFCApplicationDlg::OnScreenImageArriveCallback(MAGIMAGEHEADER srcheader, void *srcdata)  {      // construct bitmap      BITMAPINFOHEADER bmif;      bmif.biSize = sizeof(BITMAPINFOHEADER);      bmif.biHeight = srcheader.height;      bmif.biWidth = srcheader.width;      bmif.biSizeImage = bmif.biWidth*bmif.biHeight * 4;      bmif.biPlanes = 1;      bmif.biBitCount = (WORD)(bmif.biSizeImage / bmif.biHeight / bmif.biWidth * 8);      bmif.biCompression = BI_RGB;        BITMAPFILEHEADER bmfh;      LONG offBits = sizeof(BITMAPFILEHEADER) + bmif.biSize;      bmfh.bfType = 0x4d42; // "BM"      bmfh.bfOffBits = offBits;      bmfh.bfSize = offBits + bmif.biSizeImage;      bmfh.bfReserved1 = 0;      bmfh.bfReserved2 = 0;        // 返回的圖片數據存儲是從上到下,位圖要求是從下到上,所以需要調整      LPBYTE pData = (BYTE*)new BYTE[bmif.biSizeImage];      memcpy(pData, (LPBYTE)srcdata+srcheader.offset, bmif.biSizeImage);      LONG nLineSize = bmif.biWidth * bmif.biBitCount / 8;      BYTE* pLineData = new BYTE[nLineSize];      LONG nLineStartIndex = 0;      LONG nLineEndIndex = bmif.biHeight - 1;      while (nLineStartIndex < nLineEndIndex)      {          BYTE* pStart = pData + (nLineStartIndex * nLineSize);          BYTE* pEnd = pData + (nLineEndIndex * nLineSize);          memcpy(pLineData, pStart, nLineSize);          memcpy(pStart, pEnd, nLineSize);          memcpy(pEnd, pLineData, nLineSize);          nLineStartIndex++;          nLineEndIndex--;      }      delete[] pLineData;      pLineData = nullptr;        std::ofstream ofs(L"C:\1.bmp", fstream::out|fstream::binary);      if (ofs.is_open())      {          ofs.write((const char*)&bmfh, sizeof(BITMAPFILEHEADER));          ofs.write((const char*)&bmif, sizeof(BITMAPINFOHEADER));          ofs.write((const char*)pData, bmif.biSizeImage);          ofs.close();      }        delete[] pData;      pData = nullptr;  }