最近学习了Windows下的屏幕截图,延伸到窗口截图,其中也遇到了不少的坑,好在最后还是实现了我的目标。顺便封装了一下Win32的api,方便后续使用。
首先是先找了MS官方的屏幕截图示例,不过并没有很符合我的要求,不过也受到启发,使用窗口DC获取窗口图像。
为了将屏幕和窗口进行统一,我们定义一个结构体
//1. 屏幕的hwnd为NULL,这时截图的区域由rect指定
//2. 窗口的hwnd不为空,这时可以根据API获取窗口的大小
// 为了将屏幕和窗口进行统一,因此使用了结构体
struct WindowInfo
{
HWND hwnd; /* 为空表示屏幕截图 */
std::string desc; // 窗口标题
RECT rect{ 0,0,0,0 }; /* hwnd不为空时,此参数无效 */
};
// 判断当前鼠标指向哪个窗口
HWND GetCurPointedWindow()
{
POINT point;
BOOL ret = GetCursorPos(&point);
if (ret) {
HWND hwnd = WindowFromPoint(point);
if (hwnd != nullptr && hwnd != INVALID_HANDLE_VALUE) {
return hwnd;
}
}
return nullptr;
}
封装好的枚举类,使用示例在后面:
class Enumerator
{
public:
using EnumCallback = std::function<void(const WindowInfo &)>;
static bool EnumMonitor(EnumCallback callback)
{
// 调用Win32Api进行显示器遍历
return ::EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, (LPARAM)&callback);
}
static bool EnumWindow(EnumCallback callback)
{
// 调用Win32Api进行窗口遍历
return ::EnumWindows(EnumWindowsProc, (LPARAM)&callback);
}
private:
static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
//::GetParent获取的有可能是所有者窗口,因此使用GetAncestor获取父窗口句柄
HWND parent = ::GetAncestor(hwnd, GA_PARENT);
HWND desktop = ::GetDesktopWindow(); // 获取桌面的句柄
TCHAR szTitle[MAX_PATH] = { 0 };
::GetWindowText(hwnd, szTitle, MAX_PATH); // 获取标题
// 排除父窗口不是桌面的
if (parent != nullptr && parent != desktop) return TRUE;
// 排除标题为空的
if (wcscmp(szTitle, L"") == 0) return TRUE;
// 排除最小化窗口(因为获取最小化窗口的区域数据是不对的,因此也没办法进行截图等操作)
if (::IsIconic(hwnd)) return TRUE;
// 排除不可见窗口,被其他窗口遮挡的情况是可见的
if (!::IsWindowVisible(hwnd)) return TRUE;
// 排除对用户隐形的窗口,参考[https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute]
DWORD flag = 0;
DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &flag, sizeof(flag));
if (flag) return TRUE;
if (lParam) {
WindowInfo wnd_info{ hwnd,(LPCSTR)CT2A(szTitle, CP_ACP) };
EnumCallback * callback_ptr = reinterpret_cast<EnumCallback *>(lParam);
callback_ptr->operator()(wnd_info);
}
return TRUE;
}
static BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
MONITORINFOEX mi;
mi.cbSize = sizeof(MONITORINFOEX);
GetMonitorInfo(hMonitor, &mi);
if (dwData) {
std::string device_name = (LPCSTR)CT2A(mi.szDevice, CP_ACP);
if (mi.dwFlags == MONITORINFOF_PRIMARY) device_name += "(Primary)"; // 主显示器,可根据需要进行操作
WindowInfo wnd_info{ nullptr, device_name, mi.rcMonitor };
EnumCallback *callback = reinterpret_cast<EnumCallback *>(dwData);
(*callback)(wnd_info);
}
return TRUE;
}
};
封装好的截图类:
class WindowCapture
{
public:
using BitmapPtr = std::shared_ptr<Gdiplus::Bitmap>;
static BitmapPtr Capture(const WindowInfo &wnd_info)
{
HDC hWndDC = GetWindowDC(wnd_info.hwnd);
RECT capture_rect{ 0,0,0,0 }; // 最终要截取的区域
RECT wnd_rect; // 窗口区域
RECT real_rect; // 真实的窗口区域,实际上也不是百分百准确
if (wnd_info.hwnd) {
::GetWindowRect(wnd_info.hwnd, &wnd_rect);
DwmGetWindowAttribute(wnd_info.hwnd, DWMWINDOWATTRIBUTE::DWMWA_EXTENDED_FRAME_BOUNDS, &real_rect, sizeof(RECT));
int offset_left = real_rect.left - wnd_rect.left;
int offset_top = real_rect.top - wnd_rect.top;
capture_rect = RECT{ offset_left,offset_top,real_rect.right - real_rect.left + offset_left,real_rect.bottom - real_rect.top + offset_top };
} else {
capture_rect = wnd_info.rect;
}
int width = capture_rect.right - capture_rect.left;
int height = capture_rect.bottom - capture_rect.top;
HDC hMemDC = CreateCompatibleDC(hWndDC);
HBITMAP hBitmap = CreateCompatibleBitmap(hWndDC, width, height);
SelectObject(hMemDC, hBitmap);
BitmapPtr bitmap;
// 获取指定区域的rgb数据
bool ok = BitBlt(hMemDC, 0, 0, width, height, hWndDC, capture_rect.left, capture_rect.top, SRCCOPY);
// hBitmap就是得到的图片对象,转GDI的Bitmap进行保存
if (ok) bitmap = std::make_shared<Gdiplus::Bitmap>(hBitmap, nullptr);
DeleteDC(hWndDC);
DeleteDC(hMemDC);
DeleteObject(hBitmap);
return bitmap;
}
};
namespace TestCase
{
void Run()
{
std::vector<WindowInfo> window_vec; // 用来保存窗口信息
// 枚举显示器
Enumerator::EnumMonitor([&window_vec](const WindowInfo &wnd_info)
{
window_vec.push_back(wnd_info);
});
// 计算生成所有屏幕加在一起的区域大小
if (window_vec.size() > 0) { // 也可大于1,这样只有一个显示器时不会显示全屏选项
int width = 0, height = 0;
for (const auto &wnd_info : window_vec) {
width += wnd_info.rect.right - wnd_info.rect.left;
int h = wnd_info.rect.bottom - wnd_info.rect.top;
if (h > height) height = h; // 高度可能不一样,需要以最高的为准
}
WindowInfo wnd_info{ nullptr, "FullScreen", { 0, 0, width, height} };
window_vec.push_back(wnd_info);
}
// 枚举窗口
Enumerator::EnumWindow([&window_vec](const WindowInfo &wnd_info)
{
window_vec.push_back(wnd_info);
});
// 示例: 遍历找到的所有窗口,将每一个都截图到指定路径,文件夹需存在,程序不会自己创建文件夹
int cnt = 1;
for (const auto &window : window_vec) {
printf("%2d. %s\n", cnt, window.desc.c_str());
auto bitmap = WindowCapture::Capture(window);
if (bitmap) GdiplusUtil::SaveBitmapAsPng(bitmap, std::to_string(cnt) + ".png");
++cnt;
}
}
}
运行图就不贴了,自己下载代码运行一下即可。
这两点影响还是比较小,大部分情况下是正常的。如果你解决了的话,请评论告诉我一下,谢谢!
码字和代码不易,如果对你有用就点个赞吧!谢谢!
代码下载-CSDN(免费)