在某些业务场景下希望截全屏时不显示某些窗口特别是自身应用的窗口,比如在屏幕共享时不希望将自己应用的主界面、工具条等共享给对方。
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;
}