Win32使用SetDIBitsToDevice绘制位图时部分尺寸图像显示异常
问题描述
问题概述
我尝试通过输入数组(char* [w×h×3],格式为像素1蓝色、像素1绿色、像素1红色、像素2蓝色……)绘制w×h尺寸的图像。部分尺寸(如196×196)的图像能正常显示,但其他尺寸(如174×174)的图像显示错误,该问题表现完全随机。
Win32绘图实现
生成纯红色图像示例代码
char* imgData = new char[w * h * 3]; char* rData = new char[w * h]; char* gData = new char[w * h]; char* bData = new char[w * h]; for (int i = 0; i < w * h; ++i) { rData[i] = 255; gData[i] = 0; bData[i] = 0; } for (int i = 0; i < w * h; ++i) { imgData[3*i] = bData[i]; imgData[3*i+1] = gData[i]; imgData[3*i+2] = rData[i]; }
WM_PAINT消息中的绘图逻辑
// // FUNCTION: WndProc(HWND, unsigned, WORD, LONG) // // PURPOSE: Processes messages for the main window. // WM_PAINT - Paint the main window LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; RECT rt; GetClientRect(hWnd, &rt); switch (message) { case WM_PAINT: { hdc = BeginPaint(hWnd, &ps); BITMAPINFO bmi; CBitmap bitmap; memset(&bmi,0,sizeof(bmi)); bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader); bmi.bmiHeader.biWidth = inImage.getWidth(); bmi.bmiHeader.biHeight = -inImage.getHeight(); // Use negative height. DIB is top-down. bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 24; bmi.bmiHeader.biCompression = BI_RGB; bmi.bmiHeader.biSizeImage = inImage.getWidth()*inImage.getHeight(); SetDIBitsToDevice( hdc, 0, 100, inImage.getWidth(), inImage.getHeight(), 0, 0, 0, inImage.getHeight(), inImage.getImageData(), //imgData &bmi, DIB_RGB_COLORS); EndPaint(hWnd, &ps); } break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
其他相关设置
窗口类注册代码
// // PURPOSE: Registers the window class. // ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = (WNDPROC)WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_IMAGE); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = (LPCSTR)IDC_IMAGE; wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL); return RegisterClassEx(&wcex); }
窗口初始化代码
// //PURPOSE: Saves instance handle and creates main window BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { HWND hWnd; hInst = hInstance; // Store instance handle in our global variable hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); if (!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }
示例情况
示例1(纯色图像)
- 174×174尺寸:显示异常
- 196×196尺寸:显示正常
示例2(含图形图像)
- 450×450尺寸:显示异常
- 256×256尺寸:显示正常
解决方案
问题根源在于未遵循Win32 DIB(设备无关位图)的行对齐要求,以及BITMAPINFO参数计算错误。
1. 修复biSizeImage参数计算
Win32要求DIB的每行像素数据字节数必须是4的倍数(DWORD对齐)。24位色深下,原始每行字节数为宽度×3,需向上取整到最近的4的倍数。当前代码中biSizeImage仅计算宽度×高度,完全错误,正确计算方式如下:
int width = inImage.getWidth(); int height = inImage.getHeight(); // 计算对齐后的每行字节数 int rowBytes = ((width * 3 + 3) / 4) * 4; // 总图像数据大小 = 对齐后每行字节数 × 高度 bmi.bmiHeader.biSizeImage = rowBytes * height;
2. 调整图像数据缓冲区的分配与填充
当前imgData按宽度×3×高度分配,当宽度×3不是4的倍数时,行数据无法对齐,导致SetDIBitsToDevice读取数据错位。需重新分配对齐后的缓冲区,并按行填充:
int w = inImage.getWidth(); int h = inImage.getHeight(); int rowBytes = ((w * 3 + 3) / 4) * 4; char* imgData = new char[rowBytes * h]; // 按行填充图像数据 for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { int pixelIdx = y * w + x; int bufferIdx = y * rowBytes + x * 3; imgData[bufferIdx] = bData[pixelIdx]; // 蓝色通道 imgData[bufferIdx + 1] = gData[pixelIdx]; // 绿色通道 imgData[bufferIdx + 2] = rData[pixelIdx]; // 红色通道 } // 行末尾的对齐字节自动初始化为0,无需额外处理 }
3. 验证参数匹配
确认SetDIBitsToDevice的参数与DIB属性匹配:当前代码中biHeight设为负值表示从上到下的图像顺序,与数据填充逻辑一致,无需修改。
修复后,所有尺寸的图像都能正常显示——之前仅当宽度×3为4的倍数时(如196×3=588、256×3=768)能正常显示,正是因为这些尺寸刚好满足对齐要求。
内容的提问来源于stack exchange,提问作者tonyt95
相关产品推荐
相关产品推荐

