截图与复制位图的结构差异及BMP保存异常排查
Hey there! Let's break down why your code works for Paint's copied bitmaps but fails with Print Screen screenshots, and fix it up properly.
Key Differences Between the Two Clipboard Bitmap Types
- Paint's copied bitmap: Typically uses the
CF_DIBformat, which wraps a standardBITMAPINFOHEADER(40 bytes) followed by a color table (if needed) and raw pixel data. This matches the fixed-sizeDIBstruct you defined, so your code handles it without issues. - Print Screen screenshot: Usually comes in the
CF_DIBV5format, which uses aBITMAPV5HEADER(124 bytes). This extended header includes extra color space information (like ICC profiles) that your customDIBstruct can't account for. When you force-castCF_DIBV5data to yourDIBtype, you end up reading invalid values for critical fields likebiSizeImage, which breaks file size and offset calculations.
Critical Issues in Your Current Code
- Hardcoded offset (54 bytes): This only works for
BITMAPINFOHEADER(14-byte file header + 40-byte info header = 54). ForBITMAPV5HEADER, the correct offset is14 + biSize(14 is the size of yourHEADERstruct, plus the actual header size from the clipboard). - Incorrect
bfSizecalculation: Your code addsbiSizeImageto 54, but ignores the size of the info header itself and any extra data (like color tables or ICC profiles) in the DIB. This leads to a file that's too small or misaligned. - Struct mismatch: Your custom
DIBstruct doesn't supportBITMAPV5HEADERfields, so processingCF_DIBV5data results in corrupted header values.
Fixed Code That Handles Both Formats
Here's a revised version that properly supports both CF_DIB and CF_DIBV5:
#include <iostream> #include <fstream> #include <windows.h> // Use Windows-native header structures to avoid mismatches typedef BITMAPINFOHEADER DIBStandardHeader; typedef BITMAPV5HEADER DIBV5Header; typedef struct { std::uint16_t type; std::uint32_t bfSize; std::uint32_t reserved; std::uint32_t offset; } BMPFileHeader; int main() { std::cout << "Format Bitmap: " << IsClipboardFormatAvailable(CF_BITMAP) << "\n"; std::cout << "Format DIB: " << IsClipboardFormatAvailable(CF_DIB) << "\n"; std::cout << "Format DIBv5: " << IsClipboardFormatAvailable(CF_DIBV5) << "\n"; if (IsClipboardFormatAvailable(CF_DIB) || IsClipboardFormatAvailable(CF_DIBV5)) { if (OpenClipboard(NULL)) { HANDLE hClipboard = nullptr; bool isDIBV5 = false; // Prioritize CF_DIB first, fall back to CF_DIBV5 if (IsClipboardFormatAvailable(CF_DIB)) { hClipboard = GetClipboardData(CF_DIB); } else if (IsClipboardFormatAvailable(CF_DIBV5)) { hClipboard = GetClipboardData(CF_DIBV5); isDIBV5 = true; } if (hClipboard && hClipboard != INVALID_HANDLE_VALUE) { void* dibData = GlobalLock(hClipboard); if (dibData) { DWORD totalDIBSize = GlobalSize(hClipboard); BMPFileHeader fileHeader = {0}; fileHeader.type = 0x4D42; // Magic "BM" identifier fileHeader.offset = sizeof(BMPFileHeader); // Calculate correct offset and file size based on header type if (isDIBV5) { DIBV5Header* v5Header = reinterpret_cast<DIBV5Header*>(dibData); fileHeader.offset += v5Header->bV5Size; fileHeader.bfSize = sizeof(BMPFileHeader) + totalDIBSize; // Print V5 header details for debugging std::cout << "\nBITMAPV5HEADER detected:\n"; std::cout << "Header Size: " << v5Header->bV5Size << "\n"; std::cout << "Width: " << v5Header->bV5Width << "\n"; std::cout << "Height: " << v5Header->bV5Height << "\n"; std::cout << "Bit Count: " << v5Header->bV5BitCount << "\n"; } else { DIBStandardHeader* stdHeader = reinterpret_cast<DIBStandardHeader*>(dibData); fileHeader.offset += stdHeader->biSize; fileHeader.bfSize = sizeof(BMPFileHeader) + totalDIBSize; // Print standard header details for debugging std::cout << "\nBITMAPINFOHEADER detected:\n"; std::cout << "Header Size: " << stdHeader->biSize << "\n"; std::cout << "Width: " << stdHeader->biWidth << "\n"; std::cout << "Height: " << stdHeader->biHeight << "\n"; std::cout << "Bit Count: " << stdHeader->biBitCount << "\n"; } // Write the complete BMP file std::ofstream file("Test2.bmp", std::ios::out | std::ios::binary); if (file) { // Write the file header file.write(reinterpret_cast<char*>(&fileHeader), sizeof(fileHeader)); // Write the entire DIB data (header + color data + pixels) file.write(reinterpret_cast<char*>(dibData), totalDIBSize); std::cout << "\nFile saved successfully! You should be able to open it now.\n"; } else { std::cerr << "\nFailed to open file for writing.\n"; } GlobalUnlock(hClipboard); } else { std::cerr << "\nFailed to lock clipboard data.\n"; } } else { std::cerr << "\nFailed to retrieve clipboard data.\n"; } CloseClipboard(); } else { std::cerr << "\nFailed to open clipboard.\n"; } } else { std::cerr << "\nNo compatible bitmap format found in clipboard.\n"; } return 0; }
What Changed?
- Uses Windows-native headers:
BITMAPINFOHEADERandBITMAPV5HEADERensure we handle both header types without struct mismatches. - Dynamic offset calculation: Instead of hardcoding 54, we calculate the offset based on the actual header size from the clipboard data.
- Accurate file size: The total file size is
sizeof(BMPFileHeader) + GlobalSize(hClipboard), which includes all DIB data (header, color tables, ICC profiles, pixels). - Writes full DIB data: We write the entire clipboard DIB block directly, avoiding errors from manually copying individual header fields.
This code will work for both Paint's copied bitmaps and Print Screen screenshots, as it properly handles both CF_DIB and CF_DIBV5 formats.
内容的提问来源于stack exchange,提问作者Avi El
相关产品推荐
相关产品推荐

