I am using following routines to display OpenCV Mat on MFC View. And it's working well for uncropped images. But for pre-cropped images, it shows blank or weird images like the ones below:
First one is displayed on usual image software such as MS Paint and the second is on my MFC view. Difficult is that uncropped image files are displaying just fine. I am not sure this is fault of SetDIBitsToDevice or OpenCV. But clearly SetDIBitsToDevice loses constant number of bytes in each row. Anybody has any idea to fix this problem?
cv::Mat m_cvImage;
static int Bpp(cv::Mat img) { return 8 * img.channels(); }
void COpenCVTestView::OnDraw(CDC* pDC)
{
COpenCVTestDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
if(pDoc->m_cvImage.empty()) return;
// TODO: add draw code for native data here
int height=pDoc->m_cvImage.rows;
int width=pDoc->m_cvImage.cols;
uchar buffer[sizeof( BITMAPINFOHEADER ) + 1024];
BITMAPINFO* bmi = (BITMAPINFO* )buffer;
FillBitmapInfo(bmi,width,height,Bpp(pDoc->m_cvImage),0);
SetDIBitsToDevice(pDC->GetSafeHdc(), 0, 0, width,
height, 0, 0, 0, height, pDoc->m_cvImage.data, bmi,
DIB_RGB_COLORS);
}
void COpenCVTestView::FillBitmapInfo(BITMAPINFO* bmi, int width, int height, int bpp, int origin)
{
assert(bmi && width >= 0 && height >= 0 && (bpp == 8 || bpp == 24 || bpp == 32));
BITMAPINFOHEADER* bmih = &(bmi->bmiHeader);
memset(bmih, 0, sizeof(*bmih));
bmih->biSize = sizeof(BITMAPINFOHEADER);
bmih->biWidth = width;
bmih->biHeight = origin ? abs(height) : -abs(height);
bmih->biPlanes = 1;
bmih->biBitCount = (unsigned short)bpp;
bmih->biCompression = BI_RGB;
if (bpp == 8)
{
RGBQUAD* palette = bmi->bmiColors;
for (int i = 0; i < 256; i++)
{
palette[i].rgbBlue = palette[i].rgbGreen = palette[i].rgbRed = (BYTE)i;
palette[i].rgbReserved = 0;
}
}
}
As Roel says, the bitmap data needs to be DWORD aligned. This is an incomplete description of the problem however; each row of the data needs to start on a DWORD boundary. Typically you insert 0 to 3 bytes of padding at the end of each row to ensure this.
If it is inconvenient or impossible to pad the rows, simply make sure the width of the image is evenly divisible by 4.
I didn't investigate your example in detail, but be aware that the data you pass to SetDIBitsToDevice needs to be aligned on DWORD boundaries (IIRC). Maybe your 90% cases where it works are of the same size and things are accidentally correct, and when you rotate the alignment switches.
I would expect though that in this case you wouldn't see no output, but maybe the GDI driver detects the wrong alignment and doesn't do any blitting at all. Sounds reasonable.
I think I posted an example of a function that correctly aligns on DWORD boundaries for use with SetDIBitsToDevice to SO earlier, search for my posts for a code example.