WinAPI下将32位真彩色HBITMAP转换为1位单色位图的问题
HBITMAP转1位单色位图的问题修复
我正尝试在WinAPI中将HBITMAP转换为1位单色位图,但转换后的图像并非原位图的单色版本。不想使用任何额外库,以下是我的实现代码:
HBITMAP ConvertToMonochromeBitmap(HBITMAP hBmp) { BITMAP bmp; GetObject(hBmp, sizeof(BITMAP), &bmp); // Truecolor bmi BITMAPINFO bmiTrueColor = { 0 }; bmiTrueColor.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmiTrueColor.bmiHeader.biWidth = bmp.bmWidth; bmiTrueColor.bmiHeader.biHeight = -bmp.bmHeight; bmiTrueColor.bmiHeader.biPlanes = 1; bmiTrueColor.bmiHeader.biBitCount = 32; bmiTrueColor.bmiHeader.biCompression = BI_RGB; // Getting TrueColor bitmap bits BYTE* pBitmapBits = new BYTE[bmp.bmWidth * bmp.bmHeight * 4]; GetDIBits(GetDC(NULL), hBmp, 0, bmp.bmHeight, pBitmapBits, &bmiTrueColor, DIB_RGB_COLORS); // Create BYTE* array for Monochrome bitmap BYTE* pMonochromeData = new BYTE[bmp.bmWidth * bmp.bmHeight / 8]; memset(pMonochromeData, 0, bmp.bmWidth * bmp.bmHeight / 8); for (int i = 0; i < bmp.bmWidth * bmp.bmHeight; i++) { int blue = pBitmapBits[i * 4]; int green = pBitmapBits[i * 4 + 1]; int red = pBitmapBits[i * 4 + 2]; int grayscale = (red + green + blue) / 3; // Calculate the index in the monochrome bitmap. int monochromeIndex = i / 8; // Calculate the bit position within the byte. int bitOffset = 7 - (i % 8); // Set the corresponding bit in the monochrome bitmap. if (grayscale < 128) { // Set bit to 1 if color is black pMonochromeData[monochromeIndex] |= (1 << bitOffset); } else { // Set bit to 0 if color is not black pMonochromeData[monochromeIndex] &= ~(1 << bitOffset); } } BITMAPINFO bmi = { 0 }; bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = bmp.bmWidth; bmi.bmiHeader.biHeight = -bmp.bmHeight; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 1; bmi.bmiHeader.biCompression = BI_RGB; bmi.bmiHeader.biSizeImage = bmp.bmWidth * bmp.bmHeight / 8; HBITMAP newBitmap = CreateDIBitmap(GetDC(NULL), &bmi.bmiHeader, CBM_INIT, pMonochromeData, &bmi, DIB_RGB_COLORS); delete[] pMonochromeData; delete[] pBitmapBits; return newBitmap; }
问题分析与修复点
你的代码存在几个关键问题,导致转换后的单色位图显示异常:
- 行字节对齐错误
WinAPI要求DIB的每行字节数必须是4的倍数,直接用bmp.bmWidth / 8计算会导致宽度非8倍数时行数据错位,正确计算方式为:
int monochromeWidthBytes = ((bmp.bmWidth + 7) / 8 + 3) & ~3;
缺失1位位图颜色表
1位单色位图必须提供包含黑白两个颜色项的颜色表,否则系统会用默认颜色映射导致显示异常,需要初始化bmi.bmiColors数组。GetDC(NULL)的资源泄漏
直接调用GetDC(NULL)获取屏幕DC后未释放,会造成GDI资源泄漏,应创建临时兼容DC并在使用后释放。灰度计算精度不足
简单的(R+G+B)/3平均法不如加权灰度公式(0.299*R + 0.587*G + 0.114*B)准确,后者更符合人眼对颜色的感知,能减少色差。
修复后的完整代码
HBITMAP ConvertToMonochromeBitmap(HBITMAP hBmp) { BITMAP bmp; GetObject(hBmp, sizeof(BITMAP), &bmp); // 创建临时兼容DC,避免使用屏幕DC的问题 HDC hTempDC = CreateCompatibleDC(NULL); HBITMAP hOldBmp = (HBITMAP)SelectObject(hTempDC, hBmp); // Truecolor bmi BITMAPINFO bmiTrueColor = { 0 }; bmiTrueColor.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmiTrueColor.bmiHeader.biWidth = bmp.bmWidth; bmiTrueColor.bmiHeader.biHeight = -bmp.bmHeight; bmiTrueColor.bmiHeader.biPlanes = 1; bmiTrueColor.bmiHeader.biBitCount = 32; bmiTrueColor.bmiHeader.biCompression = BI_RGB; // 分配32位位图数据缓冲区 DWORD dwTrueColorSize = bmp.bmWidth * bmp.bmHeight * 4; BYTE* pBitmapBits = new BYTE[dwTrueColorSize]; GetDIBits(hTempDC, hBmp, 0, bmp.bmHeight, pBitmapBits, &bmiTrueColor, DIB_RGB_COLORS); // 计算单色位图每行字节数(4字节对齐) int monochromeWidthBytes = ((bmp.bmWidth + 7) / 8 + 3) & ~3; DWORD dwMonochromeSize = monochromeWidthBytes * bmp.bmHeight; BYTE* pMonochromeData = new BYTE[dwMonochromeSize]; memset(pMonochromeData, 0xFF, dwMonochromeSize); // 默认填充白色(bit 0) for (int y = 0; y < bmp.bmHeight; y++) { for (int x = 0; x < bmp.bmWidth; x++) { int pixelIndex = y * bmp.bmWidth + x; BYTE blue = pBitmapBits[pixelIndex * 4]; BYTE green = pBitmapBits[pixelIndex * 4 + 1]; BYTE red = pBitmapBits[pixelIndex * 4 + 2]; // 加权灰度计算,更符合人眼感知 int grayscale = (int)(0.299 * red + 0.587 * green + 0.114 * blue); // 计算当前像素在单色缓冲区中的位置 int byteOffset = y * monochromeWidthBytes + (x / 8); int bitPos = 7 - (x % 8); // 灰度低于128则设为黑色(bit 1),否则保持白色(bit 0) if (grayscale < 128) { pMonochromeData[byteOffset] &= ~(1 << bitPos); } } } // 初始化单色位图的BITMAPINFO,包含颜色表 BITMAPINFO bmi = { 0 }; bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = bmp.bmWidth; bmi.bmiHeader.biHeight = -bmp.bmHeight; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 1; bmi.bmiHeader.biCompression = BI_RGB; bmi.bmiHeader.biSizeImage = dwMonochromeSize; // 设置颜色表:索引0=白色,索引1=黑色 bmi.bmiColors[0].rgbRed = 255; bmi.bmiColors[0].rgbGreen = 255; bmi.bmiColors[0].rgbBlue = 255; bmi.bmiColors[1].rgbRed = 0; bmi.bmiColors[1].rgbGreen = 0; bmi.bmiColors[1].rgbBlue = 0; // 创建单色位图 HBITMAP newBitmap = CreateDIBitmap(hTempDC, &bmi.bmiHeader, CBM_INIT, pMonochromeData, &bmi, DIB_RGB_COLORS); // 释放资源 SelectObject(hTempDC, hOldBmp); DeleteDC(hTempDC); delete[] pMonochromeData; delete[] pBitmapBits; return newBitmap; }
内容的提问来源于stack exchange,提问作者Anwdy
相关产品推荐
相关产品推荐

