在 MFC 專案中整合 OpenCV (舊版 C 介面 `IplImage`) 時,常需將影像資料繪製至 MFC 視窗或轉為 `CBitmap` 進行 GDI 操作。本文提供基於 `CreateDIBitmap` 的零拷貝 (或最小拷貝) 轉換函數,並解析為何 `CDC::StretchBlt` 失效而 `StretchDIBits` 成功的關鍵差異。
CBitmap* IplImageToCBitmap(IplImage* pImg, CDC* pDC) {
if (!pImg || pImg->depth != IPL_DEPTH_8U) return nullptr;
// 1. 建立 BITMAPINFOHEADER (含調色盤)
int width = pImg->width;
int height = pImg->height;
int bpp = (pImg->depth & 255) * pImg->nChannels; // 8/24/32
int origin = pImg->origin; // 0=底部優先, 1=頂部優先
// 分配足夠空間給 BITMAPINFOHEADER + 調色盤 (最大 256*4 bytes)
BYTE buffer[sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)];
BITMAPINFO* pBMI = (BITMAPINFO*)buffer;
BITMAPINFOHEADER* pBMIH = &pBMI->bmiHeader;
memset(pBMIH, 0, sizeof(*pBMIH));
pBMIH->biSize = sizeof(BITMAPINFOHEADER);
pBMIH->biWidth = width;
pBMIH->biHeight = origin ? abs(height) : -abs(height); // 正高度=底部優先 (DIB), 負高度=頂部優先 (BMP)
pBMIH->biPlanes = 1;
pBMIH->biBitCount = (WORD)bpp;
pBMIH->biCompression = BI_RGB;
// 8 位灰階需填入灰階調色盤
if (bpp == 8) {
RGBQUAD* palette = pBMI->bmiColors;
for (int i = 0; i < 256; ++i) {
palette[i].rgbBlue = palette[i].rgbGreen = palette[i].rgbRed = (BYTE)i;
palette[i].rgbReserved = 0;
}
}
// 2. 關鍵:CreateDIBitmap 需要「目標裝置內容」以正確處理調色盤實現
HBITMAP hBmp = CreateDIBitmap(
pDC->GetSafeHdc(), // 必須傳入有效 HDC,NULL 會建立裝置無關位圖 (DIB Section) 導致調色盤錯誤
pBMIH,
CBM_INIT,
pImg->imageData, // 直接使用 IplImage 資料指標 (零拷貝)
pBMI,
DIB_RGB_COLORS
);
if (!hBmp) return nullptr;
// 3. 封裝為 CBitmap
CBitmap* pBmp = new CBitmap;
pBmp->Attach(hBmp);
return pBmp; // 呼叫端負責 delete
}
CvvImage::DrawToHDC 內部呼叫 ::StretchDIBits,直接將 **DIB 記憶體資料** 繪製至目標 DC,繞過 GDI 位圖物件選入機制。CDC::StretchBlt 要求來源 DC 已選入一個 **相容位圖 (DDB)**。若直接將 `IplImage` 資料 `GetBitmapBits` 寫入 `CBitmap`,該位圖可能為 **DIB Section** 而非裝置相容位圖,導致 `StretchBlt` 失敗 (返回 0,`GetLastError`=ERROR_INVALID_HANDLE)。IplImage* CBitmapToIplImage(CBitmap* pBmp) {
BITMAP bm; pBmp->GetBitmap(&bm);
IplImage* pImg = cvCreateImage(cvSize(bm.bmWidth, bm.bmHeight), IPL_DEPTH_8U, bm.bmBitsPixel / 8);
// 注意:bm.bmBits 可能為 NULL (DDB 無法直接存取),需先 GetDIBits
// 簡易版假設為 DIB Section:
pBmp->GetBitmapBits(pImg->imageSize, pImg->imageData);
return pImg; // 呼叫端需 cvReleaseImage
}
OpenCV 2.0+ 強烈建議使用 C++ 介面 cv::Mat,配合 cv::cvarrToMat 互轉。MFC 整合時可直接用 Mat 資料指標建立 `CImage` 或 `HBITMAP`,程式碼更簡潔且具備參考計數自動管理記憶體。