GetDIBits() fails when using bitmap from BitBlt()

Started by
7 comments, last by Anon Mike 17 years, 9 months ago
I have a function in my custom Bitmap class called CaptureScreen() which bit-blits the screen to a compatible HBITMAP, then calls GetDIBits() to store the pixel data in a buffer. There are two calls to GetDIBits(), the first getting the BITMAPINFO structure filled up, and the second getting the actual pixel data. The problem is that the first call to GetDIBits() fails, and GetLastError() returns 6, ERROR_INVALID_HANDLE. I cannot figure out why. Here is the code: CaptureScreen():
int Bitmap::CaptureScreen(RECT rect)  {
	Release();//Release the last information just in case
	if(rect.right==-1)rect.right=GetSystemMetrics(SM_CXSCREEN)-1;//set defaults
	if(rect.bottom==-1)rect.bottom=GetSystemMetrics(SM_CYSCREEN)-1;

	HDC scrDC=CreateDC("DISPLAY\0",0,0,0);
	HDC comDC=CreateCompatibleDC(scrDC);
	if(!scrDC || !comDC)return 1;
	HBITMAP hbmp=CreateCompatibleBitmap(scrDC,rect.right-rect.left+1,
		rect.bottom-rect.top+1);
	if(!hbmp)  {
		DeleteDC(comDC);
		DeleteDC(scrDC);
		return 1;
	}
	if(!SelectObject(comDC,hbmp))  {
		DeleteObject(hbmp);
		DeleteDC(comDC);
		DeleteDC(scrDC);
		return 1;
	}
	if(!BitBlt(comDC,0,0,rect.right-rect.left+1,rect.bottom-rect.top+1,scrDC,rect.left,
		rect.right,SRCCOPY))  {
		DeleteObject(hbmp);
		DeleteDC(comDC);
		DeleteDC(scrDC);
		return 1;
	}
	BITMAPINFO bi;
	bi.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
	if(!GetDIBits(comDC,hbmp,0,0,0,&bi,DIB_RGB_COLORS))  {//returns 0
		MessageBoxNum(GetLastError());
//displays 6, aka ERROR_INVALID_HANDLE. I cannot figure out which parameter is bad.
		DeleteObject(hbmp);
		DeleteDC(comDC);
		DeleteDC(scrDC);
		return 1;
	}
	infoHeader=bi.bmiHeader;
	pixel=new PIXEL[infoHeader.biSizeImage/sizeof(PIXEL)];
	if(!GetDIBits(comDC,hbmp,0,infoHeader.biHeight,pixel,&bi,DIB_RGB_COLORS))  {
		DeleteObject(hbmp);
		DeleteDC(comDC);
		DeleteDC(scrDC);
		return 1;
	}
	DeleteObject(hbmp);
	DeleteDC(comDC);
	DeleteDC(scrDC);
	return 0;
}








Bitmap Class:
	struct PIXEL  {
		BYTE b,g,r;
	};
	class Bitmap  {
	//...some functions
	protected:
		BITMAPINFOHEADER infoHeader;
		PIXEL* pixel;
	};
So it seems to be creating DCs and bit blitting okay. I think there is something wrong with my call to GetDIBits or my BITMAPINFO declaration, but I'm not sure. I tried using scrDC in the GetDIBits call (as opposed to comDC), but it doesn't work either (I also tried calling SelectObject(comDC,0)... doesn't work). Am I missing something obvious? Edit- Just for ease of reading, here's the CaptureScreen() function without error handling code:
	//CREATE OBJECTS
	HDC scrDC=CreateDC("DISPLAY\0",0,0,0);//create DC for screen
	HDC comDC=CreateCompatibleDC(scrDC);//create DC for bitmap
	HBITMAP hbmp=CreateCompatibleBitmap(scrDC,rect.right-rect.left+1,
		rect.bottom-rect.top+1);//create bitmap
	SelectObject(comDC,hbmp);

	//BIT BLIT
	BitBlt(comDC,0,0,rect.right-rect.left+1,rect.bottom-rect.top+1,scrDC,rect.left,rect.right,SRCCOPY);
	//^^copy screen to bitmap

	//GET DEVICE INDEPENDENT BITS
	BITMAPINFO bi;
	bi.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);//give its own size
	GetDIBits(comDC,hbmp,0,0,0,&bi,DIB_RGB_COLORS);//fill bi with info
	//this^^^ is the function that fails
	infoHeader=bi.bmiHeader;//copy the info header
	pixel=new PIXEL[infoHeader.biSizeImage/sizeof(PIXEL)];//allocate space
	GetDIBits(comDC,hbmp,0,infoHeader.biHeight,pixel,&bi,DIB_RGB_COLORS);
	//get pixel data

	//delete created objects
	DeleteObject(hbmp);
	DeleteDC(comDC);
	DeleteDC(scrDC);
	return 0;
Grab life by the cord.
Advertisement
GetDIBits(comDC,hbmp,0,0,0,&bi,DIB_RGB_COLORS);

Do you need the DIB_RGB_COLORS flag? Also I was reading on msdn and it was saying that the bit count in the BITMAPINFO structure needs to be zero, it probably is already though.
Wow... okay so first of all, DIB_RGB_COLORS is 0, so I'm not sure what else I would put there (the other option, DIB_PAL_COLORS, isn't what I want).

When I include the line
bi.bmiHeader.biBitCount=0;
before the first GetDIBits() call, I get a debug error popup, saying "Run-Time Check Failure #2 - Stack around the variable 'bi' was corrupted."

I'm thinking there's something wrong with the declaration of the bi variable. Any ideas?

Edit- The program only crashes when I call GetDIBits(), so GetDIBits() is probably writing on the stack before or after bi, but I'm not sure why.
Grab life by the cord.
You pretty much always need to set size parameters for windows API calls, so that's like the problem here. Try initializing bi.bmiHeader.biSize before making the call
"Walk not the trodden path, for it has borne it's burden." -John, Flying Monk
Original post by CrazyCamel
bi.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);//give its own size


Original post by Extrarius
You pretty much always need to set size parameters for windows API calls, so that's like the problem here. Try initializing bi.bmiHeader.biSize before making the call

=)

Edit- The quote button doesn't seem to work...
Grab life by the cord.
Hrmm, missed that line somehow.

Try doing a ZeroMemory on the structure before setting the size. That's generally a good idea also.

If that doesn't work, do the following instead:
1) Create memory DC
2) CreateDIBSection
3) Select DIB handle into memory DC
4) BitBlt from screen DC to memory DC
5) GDIFlush
6) Copy data from DIB to where you want it using the pointer given to you by CreateDIBSection
"Walk not the trodden path, for it has borne it's burden." -John, Flying Monk
Quote:Try doing a ZeroMemory on the structure before setting the size. That's generally a good idea also.

That didn't work.

Quote:If that doesn't work, do the following instead:


But as for the alternate (workaround?) method, it works nicely.

int Bitmap::CaptureScreen(RECT rect)  {	Release();	if(rect.right==-1)rect.right=GetSystemMetrics(SM_CXSCREEN)-1;	if(rect.bottom==-1)rect.bottom=GetSystemMetrics(SM_CYSCREEN)-1;	HDC scrDC=CreateDC("DISPLAY\0",0,0,0);	HDC comDC=CreateCompatibleDC(scrDC);	if(!scrDC || !comDC)return 1;	BITMAPINFO bi;	memset((void*)&bi,0,sizeof(BITMAPINFO));	bi.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);	bi.bmiHeader.biWidth=rect.right-rect.left+1;	bi.bmiHeader.biHeight=rect.bottom-rect.top+1;	bi.bmiHeader.biPlanes=1;	bi.bmiHeader.biBitCount=24;	bi.bmiHeader.biCompression=BI_RGB;	bi.bmiHeader.biSizeImage=bi.bmiHeader.biWidth*bi.bmiHeader.biHeight*		bi.bmiHeader.biBitCount/8;	bi.bmiHeader.biXPelsPerMeter=0;	bi.bmiHeader.biYPelsPerMeter=0;	bi.bmiHeader.biClrUsed=0;	bi.bmiHeader.biClrImportant=0;	BYTE* pbits=0;	HBITMAP hbmp=CreateDIBSection(comDC,&bi,DIB_RGB_COLORS,(void**)&pbits,0,0);	if(!hbmp)  {		DeleteDC(comDC);		DeleteDC(scrDC);		return 1;	}	if(!SelectObject(comDC,hbmp))  {		DeleteObject(hbmp);		DeleteDC(comDC);		DeleteDC(scrDC);		return 1;	}	if(!BitBlt(comDC,0,0,rect.right-rect.left+1,rect.bottom-rect.top+1,scrDC,rect.left,		rect.top,SRCCOPY))  {		DeleteObject(hbmp);		DeleteDC(comDC);		DeleteDC(scrDC);		return 1;	}	if(!GdiFlush())  {		DeleteObject(hbmp);		DeleteDC(comDC);		DeleteDC(scrDC);		return 1;	}	infoHeader=bi.bmiHeader;	pixel=new PIXEL[infoHeader.biSizeImage/sizeof(PIXEL)];	if(memcpy_s(pixel,infoHeader.biSizeImage,pbits,infoHeader.biSizeImage))  {		DeleteObject(hbmp);		DeleteDC(comDC);		DeleteDC(scrDC);		return 1;	}	DeleteObject(hbmp);	DeleteDC(comDC);	DeleteDC(scrDC);}


Thanks!

I am still curious, though, about why the first method didn't work. If anyone has any ideas, please feel free to state them.

Edit- Only one problem with the code, and that's the fact that the width has to be a multiple of 4 because (according to other posts) each row has to be divisible by 4 bytes, and my format uses 3 byte (24bit) pixels. I'm looking around for a fix/workaround, and I'll edit this when I find it.
I suppose I could just memcpy() each row, leaving out the extra space, or perhaps just use 32bit format in the DIB, and then only copy the first 24bits of each pixel into my own format. The problem with these (and similar) methods is that it seems like it would make a performance problem...
Edit2- Ah, its not really a problem, I just need to change some size variables in my code to adjust for the extra bytes.

[Edited by - CrazyCamel on July 15, 2006 2:40:03 PM]
Grab life by the cord.
Quote:Original post by CrazyCamel
Edit- Only one problem with the code, and that's the fact that the width has to be a multiple of 4 because (according to other posts) each row has to be divisible by 4 bytes, and my format uses 3 byte (24bit) pixels. I'm looking around for a fix/workaround, and I'll edit this when I find it.
I suppose I could just memcpy() each row, leaving out the extra space, or perhaps just use 32bit format in the DIB, and then only copy the first 24bits of each pixel into my own format. The problem with these (and similar) methods is that it seems like it would make a performance problem...
Edit2- Ah, its not really a problem, I just need to change some size variables in my code to adjust for the extra bytes.


Erm, so once I actually found time to try the last bit, I found that it isn't quite true...

Okay so basically, I have been testing my CaptureScreen() function by capturing the screen, and then saving it to a .bmp file with a seperate function (one that works normally).

When the width of the captured image is not a multiple of 4, the saved file is fully black. Now, there may be many reasons for this, and it is most likely specific to my application, but in order to solve this, I need to know a few things first:
a) Does the CreateDIBSection() function care whether the width is a multiple of 4?
b) Does the BitBlt() function care?
c) When writing a .bmp file, if line's width is not a multiple of 4, do I just add 0s until I get up to the next multiple?
Grab life by the cord.
Your first code did not work because you didn't give GetDIBits enough information to know what format you wanted the bits in. You need to fill in biWidth, biHeight, biPlanes, biBitCount, and biCompression. e.g. adding these lines makes it work:
    bi.bmiHeader.biWidth = rect.right-rect.left+1;    bi.bmiHeader.biHeight = rect.bottom-rect.top+1;    bi.bmiHeader.biPlanes = 1;    bi.bmiHeader.biBitCount = 24;    bi.bmiHeader.biCompression = BI_RGB;

If you want some other bit depth (e.g. 32bpp) you need to adjust things appropriately.

The GetLastError thing was a red herring. Contrary to MSDN GDI and USER function basically never set the last error so you were getting essentially a random number. You can see this for yourself by calling SetLastError before the failing api and seeing that the value does not change.
-Mike

This topic is closed to new replies.

Advertisement