Windows下截图转字节数组生成BMP文件头错误,如何正确写入?
尝试使用C++和Windows API将截图数据转换为代表位图(Bitmap)图像的字节数组,然后保存到磁盘。保存后的图像无法在Windows照片查看器中打开,但可以在GIMP中打开且图像可见,不过GIMP会给出警告:gimp-image-set-resolution failed,resolution was out of range,so default resolution is used,推测位图的头数据复制存在问题。
以下是完整代码:
#include <iostream> #include <windows.h> #include <fstream> void WINAPI CaptureScreenIntoByteArray(BYTE*& screen_bytes, DWORD& screen_bytes_size) { BITMAPFILEHEADER bfHeader; BITMAPINFOHEADER biHeader; BITMAPINFO bInfo; HGDIOBJ hTempBitmap; HBITMAP hBitmap; BITMAP bAllDesktops; HDC hDC, hMemDC; LONG lWidth, lHeight; BYTE* sb = NULL; ZeroMemory(&bfHeader, sizeof(BITMAPFILEHEADER)); ZeroMemory(&biHeader, sizeof(BITMAPFILEHEADER)); ZeroMemory(&bInfo, sizeof(BITMAPINFO)); ZeroMemory(&bAllDesktops, sizeof(BITMAP)); hDC = GetDC(NULL); hTempBitmap = GetCurrentObject(hDC, OBJ_BITMAP); GetObjectW(hTempBitmap, sizeof(BITMAP), &bAllDesktops); lWidth = bAllDesktops.bmWidth; lHeight = bAllDesktops.bmHeight; DeleteObject(hTempBitmap); bfHeader.bfType = (WORD)('B' | ('M' << 8)); bfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); biHeader.biSize = sizeof(BITMAPINFOHEADER); biHeader.biBitCount = 24; biHeader.biCompression = BI_RGB; biHeader.biPlanes = 1; biHeader.biWidth = lWidth; biHeader.biHeight = lHeight; bInfo.bmiHeader = biHeader; screen_bytes_size = (((24 * lWidth + 31) & ~31) / 8) * lHeight; hMemDC = CreateCompatibleDC(hDC); hBitmap = CreateDIBSection(hDC, &bInfo, DIB_RGB_COLORS, (VOID**)&sb, NULL, 0); SelectObject(hMemDC, hBitmap); int x = GetSystemMetrics(SM_XVIRTUALSCREEN); int y = GetSystemMetrics(SM_YVIRTUALSCREEN); BitBlt(hMemDC, 0, 0, lWidth, lHeight, hDC, x, y, SRCCOPY); // copy image data, i guess something is not correctly copied screen_bytes = new BYTE[sizeof(BITMAPFILEHEADER) + sizeof (BITMAPINFOHEADER) + screen_bytes_size]; std::copy(&bfHeader, &bfHeader + 1, (BITMAPFILEHEADER*)screen_bytes); std::copy(&biHeader, &biHeader + 1, (BITMAPINFOHEADER*)(screen_bytes + sizeof(BITMAPFILEHEADER))); std::copy(sb, sb + screen_bytes_size, screen_bytes + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)); DeleteDC(hMemDC); ReleaseDC(NULL, hDC); DeleteObject(hBitmap); } int main() { //get image data array BYTE* screen_bytes = NULL; DWORD screen_bytes_size = 0; CaptureScreenIntoByteArray(screen_bytes,screen_bytes_size); //save image data array as bmp file FILE* file = fopen( "output.bmp", "wb" ); fwrite( screen_bytes, screen_bytes_size, 1, file ); fclose(file); //do further work with image data array return 0; }
问题分析与修正方案
代码存在多个导致文件格式错误的问题,逐一修正如下:
1. BITMAPINFOHEADER初始化大小错误
原代码中ZeroMemory(&biHeader, sizeof(BITMAPFILEHEADER))使用了BITMAPFILEHEADER的大小初始化BITMAPINFOHEADER,但BITMAPINFOHEADER(40字节)比BITMAPFILEHEADER(14字节)长,导致biHeader的后续字段(如分辨率相关的biXPelsPerMeter、biYPelsPerMeter)残留垃圾值,这是GIMP警告分辨率异常的原因。
修正:
ZeroMemory(&biHeader, sizeof(BITMAPINFOHEADER));
2. 未设置BITMAPFILEHEADER的bfSize字段
bfSize表示整个BMP文件的总字节数,Windows照片查看器依赖该字段识别文件完整性,必须设置为文件头+信息头+像素数据的总大小。
修正:
bfHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + screen_bytes_size;
3. fwrite写入长度错误
原代码中fwrite仅写入了screen_bytes_size(仅像素数据大小),但screen_bytes数组包含文件头、信息头和像素数据三部分,导致写入的文件缺少部分头数据,Windows照片查看器无法识别。
修正:
DWORD total_size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + screen_bytes_size; fwrite(screen_bytes, total_size, 1, file);
4. 屏幕尺寸获取方法错误
原代码通过GetCurrentObject(hDC, OBJ_BITMAP)获取的是DC默认位图尺寸,并非实际屏幕分辨率,多显示器场景下会出错,应直接使用系统API获取虚拟屏幕尺寸。
修正:
lWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN); lHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN); // 移除以下错误代码: // hTempBitmap = GetCurrentObject(hDC, OBJ_BITMAP); // GetObjectW(hTempBitmap, sizeof(BITMAP), &bAllDesktops); // DeleteObject(hTempBitmap);
修正后的完整代码
#include <iostream> #include <windows.h> #include <fstream> #include <algorithm> void WINAPI CaptureScreenIntoByteArray(BYTE*& screen_bytes, DWORD& screen_bytes_size) { BITMAPFILEHEADER bfHeader; BITMAPINFOHEADER biHeader; BITMAPINFO bInfo; HBITMAP hBitmap; HDC hDC, hMemDC; LONG lWidth, lHeight; BYTE* sb = NULL; ZeroMemory(&bfHeader, sizeof(BITMAPFILEHEADER)); ZeroMemory(&biHeader, sizeof(BITMAPINFOHEADER)); ZeroMemory(&bInfo, sizeof(BITMAPINFO)); hDC = GetDC(NULL); // 正确获取虚拟屏幕尺寸 lWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN); lHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN); // 设置文件头 bfHeader.bfType = (WORD)('B' | ('M' << 8)); bfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); // 设置信息头 biHeader.biSize = sizeof(BITMAPINFOHEADER); biHeader.biBitCount = 24; biHeader.biCompression = BI_RGB; biHeader.biPlanes = 1; biHeader.biWidth = lWidth; biHeader.biHeight = lHeight; // 初始化分辨率字段为0(或按需设置合理值) biHeader.biXPelsPerMeter = 0; biHeader.biYPelsPerMeter = 0; bInfo.bmiHeader = biHeader; // 计算像素数据大小(每行字节数按4字节对齐) screen_bytes_size = (((24 * lWidth + 31) & ~31) / 8) * lHeight; // 设置文件总大小 bfHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + screen_bytes_size; hMemDC = CreateCompatibleDC(hDC); hBitmap = CreateDIBSection(hDC, &bInfo, DIB_RGB_COLORS, (VOID**)&sb, NULL, 0); SelectObject(hMemDC, hBitmap); int x = GetSystemMetrics(SM_XVIRTUALSCREEN); int y = GetSystemMetrics(SM_YVIRTUALSCREEN); BitBlt(hMemDC, 0, 0, lWidth, lHeight, hDC, x, y, SRCCOPY); // 分配完整的字节数组空间 screen_bytes = new BYTE[bfHeader.bfSize]; // 复制文件头 std::copy(reinterpret_cast<BYTE*>(&bfHeader), reinterpret_cast<BYTE*>(&bfHeader) + sizeof(BITMAPFILEHEADER), screen_bytes); // 复制信息头 std::copy(reinterpret_cast<BYTE*>(&biHeader), reinterpret_cast<BYTE*>(&biHeader) + sizeof(BITMAPINFOHEADER), screen_bytes + sizeof(BITMAPFILEHEADER)); // 复制像素数据 std::copy(sb, sb + screen_bytes_size, screen_bytes + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)); DeleteDC(hMemDC); ReleaseDC(NULL, hDC); DeleteObject(hBitmap); } int main() { BYTE* screen_bytes = NULL; DWORD screen_bytes_size = 0; CaptureScreenIntoByteArray(screen_bytes, screen_bytes_size); FILE* file = fopen("output.bmp", "wb"); if (file != NULL) { // 写入完整的文件数据 DWORD total_size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + screen_bytes_size; fwrite(screen_bytes, total_size, 1, file); fclose(file); } // 释放内存 delete[] screen_bytes; return 0; }
内容的提问来源于stack exchange,提问作者Brandonn

