前言
實現屏幕截屏需要用到 Windows API,所以需要包括 Windows.h 頭文件。同時我們想要對截圖做進一步的處理,就需要用到 OpenCV。關於 OpenCV 的安裝與編譯可以參見 《再整理:Visual Studio Code(vscode)下的基於C++的OpenCV的最新搭建攻略解析》,親測有效,但是 OpenCV 還有 MinGW 的版本最好和博客中保持一致,不然編譯可能會失敗。下面進入正題。
代碼
頭文件
Screenshot.h
#pragma once
#include <Windows.h>
#include <opencv2/opencv.hpp>
class Screenshot
{
public:
Screenshot();
double static getZoom();
cv::Mat getScreenshot();
cv::Mat getScreenshot(int x, int y, int width, int height);
private:
int m_width;
int m_height;
HDC m_screenDC;
HDC m_compatibleDC;
HBITMAP m_hBitmap;
LPVOID m_screenshotData = nullptr;
};
源文件
在截圖之前需要獲取屏幕的分辨率,一種很直觀的想法就是調用 GetSystemMetrics(SM_C*SCREEN) 函數來獲取寬度或者高度。如下圖所示,設置屏幕縮放 125% 之后,得到的值會偏小。如果是 1920 × 1080 的分辨率,GetSystemMetrics(SM_CXSCREEN) 和 GetSystemMetrics(SM_CYSCREEN) 返回分辨率會是 (1920, 1080) / 1.25 = (1536, 864)。所以我們需要先計算屏幕的縮放率。這個任務由 Screenshot::getZoom() 完成。剩下的步驟注釋中解釋的很充分了,不再贅述。

Screenshot.cpp
#include "Screenshot.h"
using cv::Mat;
Screenshot::Screenshot()
{
double zoom = getZoom();
m_width = GetSystemMetrics(SM_CXSCREEN) * zoom;
m_height = GetSystemMetrics(SM_CYSCREEN) * zoom;
m_screenshotData = new char[m_width * m_height * 4];
memset(m_screenshotData, 0, m_width);
// 獲取屏幕 DC
m_screenDC = GetDC(NULL);
m_compatibleDC = CreateCompatibleDC(m_screenDC);
// 創建位圖
m_hBitmap = CreateCompatibleBitmap(m_screenDC, m_width, m_height);
SelectObject(m_compatibleDC, m_hBitmap);
}
/* 獲取整個屏幕的截圖 */
Mat Screenshot::getScreenshot()
{
// 得到位圖的數據
BitBlt(m_compatibleDC, 0, 0, m_width, m_height, m_screenDC, 0, 0, SRCCOPY);
GetBitmapBits(m_hBitmap, m_width * m_height * 4, m_screenshotData);
// 創建圖像
Mat screenshot(m_height, m_width, CV_8UC4, m_screenshotData);
return screenshot;
}
/** @brief 獲取指定范圍的屏幕截圖
* @param x 圖像左上角的 X 坐標
* @param y 圖像左上角的 Y 坐標
* @param width 圖像寬度
* @param height 圖像高度
*/
Mat Screenshot::getScreenshot(int x, int y, int width, int height)
{
Mat screenshot = getScreenshot();
return screenshot(cv::Rect(x, y, width, height));
}
/* 獲取屏幕縮放值 */
double Screenshot::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;
}
測試
對於 1920 × 1080 的分辨率,截一次屏在 30ms 左右,下面是測試代碼:
#include "Screenshot.h"
using namespace cv;
int main()
{
Screenshot screenshot;
Mat img = screenshot.getScreenshot();
Mat img_ = screenshot.getScreenshot(1040, 132, 800, 880);
imwrite("screenshot.jpg", img);
imwrite("screenshot_part.jpg", img_);
return 0;
}
