程序简介
项目编写的C++程序,根据输入的字符串,遍历所有桌面窗口标题,查找包含该标题的窗口,对该桌面窗口进行截图,以梦幻西游为例
输入:桌面窗口包含的字符串 比如输入“梦幻”,程序就会截取桌面“梦幻西游”的窗口
输出:该桌面窗口的截图,数据类型为opencv的Mat矩阵
程序/数据集下载
本文章只发布于博客园、爆米算法、CSDN,被抄袭后可能排版错乱或下载失效,作者:爆米LiuChen
代码环境、文件结构
VS2019 注意链接器需要加入dwmapi.lib,用来调用windows的API的opencv4.5.5
代码分析
FindWindow.h声明了查找和定位窗口的函数,定义解析请看下文
#pragma once
#include <dwmapi.h>
#include <windows.h>
#include <vector>
#include <string>
#include <iostream>
struct WindowData;
BOOL CALLBACK WindowEnumerationCallback(HWND hwnd, LPARAM lParam);
HWND getWindowHWND(std::string titleSection);
RECT getWindowLoc(HWND hwnd);
FindWindow.cpp中核心函数getWindowHWND可以根据输入的窗口标题字符串,定位到含有该字符串的窗口,返回窗口句柄HWND,然后将句柄输入getWindowLoc函数,得到窗口的位置,这里调用了windows api DwmGetWindowAttribute,如果用传统的方法GetWindowRect会得到错误的结果,因为传统方法没考虑到桌面缩放且自windows vista后的系统桌面窗口加入了“毛玻璃边缘”效果,得到的窗口位置会有偏移
#include "FindWindow.h"
struct WindowData {
HWND handle;//窗口句柄
char title[256];//窗口标题
};
std::vector<WindowData> windowDatas;
// 声明回调函数
BOOL CALLBACK WindowEnumerationCallback(HWND hwnd, LPARAM lParam) {
// 通过IsWindow函数检查窗口是否有效
if (IsWindow(hwnd)) {
// 通过IsWindowEnabled函数检查窗口是否启用
if (IsWindowEnabled(hwnd)) {
// 通过IsWindowVisible函数检查窗口是否可见
if (IsWindowVisible(hwnd)) {
// 获取窗口的文本,并打印
char windowText[256];
GetWindowTextA(hwnd, windowText, sizeof(windowText));
WindowData windowData;
windowData.handle = hwnd;
memcpy(windowData.title, windowText, 256);
windowDatas.push_back(windowData);
}
}
}
// 继续枚举其他窗口
return TRUE;
}
//返回包含titleSection的桌面窗口句柄
HWND getWindowHWND(std::string titleSection)
{
HWND handle = NULL;
//每次都要清空容器
windowDatas.clear();
// 使用EnumWindows函数枚举所有窗口,并传递给回调函数处理
EnumWindows(WindowEnumerationCallback, NULL);
//一个个找包含指定字符串的
for (auto it = windowDatas.begin(); it != windowDatas.end(); it++)
{
char title[256];
memcpy(title, it->title, 256);
std::string windowTitle(title);
if (windowTitle.find(titleSection) != std::string::npos)
{
handle = it->handle;
}
}
return handle;
}
//根据窗口句柄和桌面缩放获得窗口尺寸和位置
RECT getWindowLoc(HWND hwnd)
{
RECT frame;
DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frame, sizeof(RECT));
//std::cout << "窗口位置:(" << frame.left << ", " << frame.top << ")" << std::endl;
//std::cout << "窗口大小:(" << frame.right-frame.left << ", " << frame.bottom-frame.top << ")" << std::endl;
return frame;
}
WindowShot.h声明了根据坐标和尺寸的截图函数,定义解析请看下文
#pragma once
#include "FindWindow.h"
#include <opencv2/opencv.hpp>
struct WindowRect
{
int x;
int y;
int width;
int height;
};
class WindowShot
{
public:
WindowShot();
double static getZoom();
cv::Mat getWindowMat(std::string titleSection);
uchar* getWindowUchar(std::string titleSection);
WindowRect windowRect;
cv::Mat getDesktopMat();
~WindowShot();
private:
int width;
int height;
double zoom;
uchar* windowUchar;
RECT rect;
HDC screenDC;
HDC compatibleDC;
HBITMAP hBitmap;
LPVOID shotData;
HWND hwnd;
};
WindowShot.cpp定义了核心函数getWindowMat,该函数会调用FindWindow模块来查找窗口的句柄和位置,然后对整个屏幕具体的位置进行截图,当然同时也定义了getDesktopMat函数用来截图整个桌面,不同的是这个函数用到了个人桌面缩放率
#include "WindowShot.h"
//初始化变量
WindowShot::WindowShot()
{
zoom = getZoom();//缩放率 比如1.25
}
//根据窗口标题是否包含该字符串,获得窗口截图
cv::Mat WindowShot::getWindowMat(std::string titleSection)
{
hwnd = getWindowHWND(titleSection);
//如果窗口小化 就将其展示
if (IsIconic(hwnd)) {
ShowWindow(hwnd, SW_RESTORE);
}
SetForegroundWindow(hwnd); // 将窗口置顶
rect = getWindowLoc(hwnd); // 窗口位置
width = rect.right - rect.left;
height = rect.bottom - rect.top;
windowRect.x = rect.left;
windowRect.y = rect.top;
windowRect.width = width;
windowRect.height = height;
shotData = new char[width * height * 4];
screenDC = GetDC(NULL);// 获取屏幕 DC
compatibleDC = CreateCompatibleDC(screenDC);//兼容新DC
// 创建位图
hBitmap = CreateCompatibleBitmap(screenDC, width, height);
SelectObject(compatibleDC, hBitmap);
// 得到位图的数据
BitBlt(compatibleDC, 0, 0, width, height, screenDC, rect.left, rect.top, SRCCOPY);
GetBitmapBits(hBitmap, width * height * 4, shotData);
// 创建图像
cv::Mat windowMat(height, width, CV_8UC4, shotData);
return windowMat;
}
//根据窗口标题是否包含该字符串,获得窗口截图 将截图转为uchar* 供python使用
uchar* WindowShot::getWindowUchar(std::string titleSection)
{
cv::Mat windowMat = this->getWindowMat(titleSection);
int size = width * height * 4;
free(windowUchar);
windowUchar = (uchar*)malloc(sizeof(uchar) * size);
memcpy(windowUchar, windowMat.data, size);
return windowUchar;
}
cv::Mat WindowShot::getDesktopMat()
{
width = GetSystemMetrics(SM_CXSCREEN) * zoom;
height = GetSystemMetrics(SM_CYSCREEN) * zoom;
rect.left = 0;
rect.top = 0;
rect.right = width;
rect.bottom = height;
width = rect.right - rect.left;
height = rect.bottom - rect.top;
shotData = new char[width * height * 4];
screenDC = GetDC(NULL);// 获取屏幕 DC
compatibleDC = CreateCompatibleDC(screenDC);//兼容新DC
// 创建位图
hBitmap = CreateCompatibleBitmap(screenDC, width, height);
SelectObject(compatibleDC, hBitmap);
// 得到位图的数据
BitBlt(compatibleDC, 0, 0, width, height, screenDC, rect.left, rect.top, SRCCOPY);
GetBitmapBits(hBitmap, width * height * 4, shotData);
// 创建图像
cv::Mat desktopMat(height, width, CV_8UC4, shotData);
return desktopMat;
}
/* 获取屏幕缩放值 */
double WindowShot::getZoom()
{
// 获取窗口当前显示的监视器
HWND hWnd = GetDesktopWindow();
HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
// 获取监视器逻辑宽度
MONITORINFOEX monitorInfo;
monitorInfo.cbSize = sizeof(monitorInfo);
GetMonitorInfo(hMonitor, &monitorInfo);
int cxLogical = (monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left);
// 获取监视器物理宽度
DEVMODE dm;
dm.dmSize = sizeof(dm);
dm.dmDriverExtra = 0;
EnumDisplaySettings(monitorInfo.szDevice, ENUM_CURRENT_SETTINGS, &dm);
int cxPhysical = dm.dmPelsWidth;
return cxPhysical * 1.0 / cxLogical;
}
WindowShot::~WindowShot()
{
DeleteObject(hBitmap);
DeleteDC(compatibleDC);
}
结果展示
运行一下main.cpp,得到梦幻西游的窗口截图(文章开头已给出),对比下整个桌面截图