035-Windows抓屏-GDI

32 阅读4分钟

Windows抓屏-GDI

一、技术原理

GDI(Graphics Device Interface)抓屏基于Windows系统提供的图形设备接口,通过设备上下文(DC)

实现屏幕内容捕获。核心流程如下:

获取桌面窗口句柄:通过
//获取整个屏幕的句柄
GetDesktopWindow()
获取设备上下文:使用
//获取屏幕DC
GetDC()
创建兼容DC和位图:通过
//创建与屏幕DC兼容的绘图环境
CreateCompatibleDC()
CreateCompatibleBitmap()
位图选入兼容DC:使用
//将位图绑定到兼容DC
SelectObject()
复制屏幕内容:通过
//将屏幕DC内容复制到兼容位图
BitBlt()
释放资源:使用
//释放资源
DeleteDC()
ReleaseDC()

流程图

graph TD
A[获取桌面窗口句柄] --> B[获取设备上下文DC]
B --> C[创建兼容DC和位图]
C --> D[复制屏幕数据到兼容位图]
D --> E[保存或处理位图数据]
E --> F[释放资源]

关键组件

  • 窗口句柄(HWND):通过GetDesktopWindow()获取整个屏幕的句柄。
  • 设备上下文(DC):GetDC()获取桌面DC,用于图形操作。
  • 兼容位图(Bitmap):CreateCompatibleBitmap()创建与屏幕分辨率匹配的位图。
  • 数据复制:BitBlt()函数将屏幕像素数据复制到内存位图。

二、代码实现

全屏抓取代码
#include <windows.h>
#include <gdiplus.h>

// 全屏抓取函数
bool CaptureScreen(HWND hWnd, HBITMAP& hBitmap) {
    HDC hScreenDC = GetDC(hWnd);
    HDC hMemoryDC = CreateCompatibleDC(hScreenDC);

    // 获取屏幕分辨率
    int width = GetSystemMetrics(SM_CXSCREEN);
    int height = GetSystemMetrics(SM_CYSCREEN);

    hBitmap = CreateCompatibleBitmap(hScreenDC, width, height);
    HGDIOBJ hOldBitmap = SelectObject(hMemoryDC, hBitmap);

    // 复制屏幕内容
    if (!BitBlt(hMemoryDC, 0, 0, width, height, hScreenDC, 0, 0, SRCCOPY)) {
        DeleteObject(hBitmap);
        return false;
    }

    SelectObject(hMemoryDC, hOldBitmap);
    DeleteDC(hMemoryDC);
    ReleaseDC(hWnd, hScreenDC);
    return true;
}
局部抓取代码
// 局部区域抓取(通过窗口句柄)
bool CaptureWindow(HWND hWnd, HBITMAP& hBitmap) {
    if (!hWnd) return false;

    HDC hWindowDC = GetDC(hWnd);
    RECT rect;
    GetWindowRect(hWnd, &rect);
    int width = rect.right  - rect.left; 
    int height = rect.bottom  - rect.top; 

    HDC hMemoryDC = CreateCompatibleDC(hWindowDC);
    hBitmap = CreateCompatibleBitmap(hWindowDC, width, height);
    HGDIOBJ hOldBitmap = SelectObject(hMemoryDC, hBitmap);

    // 转换窗口坐标到屏幕坐标
    POINT pt = {0, 0};
    ScreenToClient(hWnd, &pt);
    if (!BitBlt(hMemoryDC, 0, 0, width, height, hWindowDC, pt.x, pt.y, SRCCOPY)) {
        DeleteObject(hBitmap);
        return false;
    }

    SelectObject(hMemoryDC, hOldBitmap);
    DeleteDC(hMemoryDC);
    ReleaseDC(hWnd, hWindowDC);
    return true;
}

三、完整实现示例

保存为BMP文件

void SaveBitmapToFile(HBITMAP hBitmap, const char* filename) {
    BITMAPINFOHEADER bmiHeader;
    BITMAP bmp;
    GetObject(hBitmap, sizeof(bmp), &bmp);

    // 初始化BITMAPINFOHEADER
    bmiHeader.biSize  = sizeof(BITMAPINFOHEADER);
    bmiHeader.biWidth  = bmp.bmWidth; 
    bmiHeader.biHeight  = -bmp.bmHeight;  // 倒转图像
    bmiHeader.biPlanes  = 1;
    bmiHeader.biBitCount  = 32;
    bmiHeader.biCompression  = BI_RGB;
    bmiHeader.biSizeImage  = 0;
    bmiHeader.biXPelsPerMeter  = 0;
    bmiHeader.biYPelsPerMeter  = 0;
    bmiHeader.biClrUsed  = 0;
    bmiHeader.biClrImportant  = 0;

    // 写入文件
    HANDLE hFile = CreateFileA(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) return;

    DWORD dwWritten;
    BITMAPFILEHEADER bmfHeader;
    bmfHeader.bfType  = 0x4D42; // 'BM'
    bmfHeader.bfSize  = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + bmiHeader.biSizeImage; 
    bmfHeader.bfReserved1  = 0;
    bmfHeader.bfReserved2  = 0;
    bmfHeader.bfOffBits  = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

    WriteFile(hFile, &bmfHeader, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);
    WriteFile(hFile, &bmiHeader, sizeof(BITMAPINFOHEADER), &dwWritten, NULL);

    // 获取位图数据
    DWORD dwSize = ((bmp.bmWidth  * bmiHeader.biBitCount  + 31) / 32) * 4 * bmp.bmHeight; 
    LPVOID pBits = nullptr;
    GetDIBits(GetDC(NULL), hBitmap, 0, bmp.bmHeight,  nullptr, (BITMAPINFO*)&bmiHeader, DIB_RGB_COLORS);
    GetDIBits(GetDC(NULL), hBitmap, 0, bmp.bmHeight,  pBits, (BITMAPINFO*)&bmiHeader, DIB_RGB_COLORS);
    WriteFile(hFile, pBits, dwSize, &dwWritten, NULL);

    CloseHandle(hFile);
}

四、结构图与流程图

系统架构图
+-------------------+
|     应用程序       |
+-------------------+
          |
          v
+-------------------+
|     GDI接口       |
+-------------------+
          |
          v
+-------------------+
|     设备驱动       |
+-------------------+
抓屏流程图
开始
  |
  v
获取窗口句柄
  |
  v
创建设备上下文
  |
  v
创建兼容DC和位图
  |
  v
复制屏幕内容
  |
  v
保存/释放资源
  |
  v
结束

五、优化参数与性能提升

性能瓶颈分析
  • Aero特效影响:启用Aero时,BitBlt速度下降至300-500ms/帧。
  • DC频繁创建:重复创建/销毁DC会增加开销,建议复用资源。
  • 内存占用:高分辨率屏幕(如4K)需优化内存分配。
优化方案
参数优化方法效果
双缓冲技术使用内存DC减少屏幕刷新次数降低闪烁
异步复制BitBlt操作放入独立线程提升主程序响应
位深调整使用SetDeviceCaps设置16位色彩减少内存占用
硬件加速调用EnableMenuItem禁用Aero特效提升速度至5-8ms/帧

六、性能优化参数

兼容DC优化
  • 位图格式:使用32位色深(BI_RGB)避免颜色转换开销。
  • 双缓冲技术:通过内存DC减少直接操作屏幕DC的闪烁问题。
捕获频率控制
  • 定时器间隔:通过SetTimer()控制抓帧频率(如30Hz需间隔33ms)。
  • 区域更新检测:仅当屏幕变化时触发捕获(需结合差异比较算法)。
资源管理
  • 及时释放:DeleteObject()释放位图,ReleaseDC()释放设备上下文。
  • 内存复用:复用已分配的DC和位图对象,减少重复创建开销。

七、扩展功能实现

鼠标光标叠加
CURSORINFO cursorInfo = { sizeof(cursorInfo) };
GetCursorInfo(&cursorInfo);
DrawIcon(hdcMem, cursorInfo.ptScreenPos.x,  cursorInfo.ptScreenPos.y,  cursorInfo.hCursor);
图像保存

使用GDI+库将HBITMAP保存为PNG或JPEG:

Gdiplus::Bitmap bitmap(hBitmap, NULL);
CLSID pngClsid;
GetEncoderClsid(L"image/png", &pngClsid);
bitmap.Save(L"screenshot.png",  &pngClsid);

八、技术对比与局限性

技术指标GDI抓屏DXGI抓屏(推荐替代方案)
兼容性支持所有Windows版本Win8+,需DirectX 11支持
效率1080P约5-8ms(无Aero)1080P约1-3ms
功能限制无法捕获硬件加速内容(如游戏)支持DirectX/D3D内容捕获
开发复杂度简单(纯Win32 API)复杂(需DXGI接口知识)

九、流程图与结构图

GDI抓屏架构
graph LR
UserApp --> GDI_API
GDI_API --> Win32K.sys 
Win32K.sys  --> DisplayDriver
DisplayDriver --> FrameBuffer
性能优化路径
graph TB
A[高CPU占用] --> B[降低捕获频率]
A --> C[减少位图深度]
A --> D[复用资源对象]
B --> E[动态调整帧率]
C --> F[使用16/24位色]
D --> G[全局DC池管理]

完整代码

Github

作者郑天佐
邮箱zhengtianzuo06@163.com
主页www.zhengtianzuo.com
githubgithub.com/zhengtianzu…







加QQ好友






加微信好友
关注微信公众号






支付宝赞助






微信赞助