Click to See Complete Forum and Search --> : Getting pixel Data from a Bitmap Handle


robione
December 1st, 2009, 08:17 AM
I have searched long and hard on Google and in these forums. It seems to be a common problem but I'm either something is going over my head or what I saw doesn't quite address what I'm doing. I'm using VC++ 98. I was hoping to have a "fast" app but the more I look at it, it seems I have a few wasteful steps..... that, and I'm not seeing anything resembling my screen in my app.

So what I'm doing is doing a screen capture as fast as my computer can handle it and then implementing Laplace edge detection on the color bitmap. The source is 32bit and the destination is 8bit. I'm using opengl and I'm getting like a checkerboard pattern... nothing that even resembles my screen. If I remove the laplace code and just say the color of the pixel is 128.... I should have a 2 pixel black border around that (because the mask is 5x5). That doesn't even happen.

My real concern though is actually copying the screen and accessing the color data. The rest is extra. I don't have a good understanding of the bitmap functions. What I've done is implemented what I gathered from my searches. The reason I said earlier that I think I'm wasting CPU cycles is that I think I'm copying the pixel data twice.

So the code is so long I took out some areas... wndproc.... showwindow... etc. the basic stuff.


HDC hDC;

int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
HWND hWnd;
HDC hDesktopDC = ::GetDC(::GetDesktopWindow());
HDC hMemDC = ::CreateCompatibleDC(hDesktopDC);
int iHRes = ::GetDeviceCaps(hDesktopDC,DESKTOPHORZRES),
iVRes = ::GetDeviceCaps(hDesktopDC,DESKTOPVERTRES);
HBITMAP hBMP = CreateCompatibleBitmap(hDesktopDC,iHRes,iVRes);
MSG msg;

BYTE *bySrcData = new unsigned char[iHRes*iVRes*4],
*byDestData = new unsigned char[iHRes*iVRes*4];

BITMAPINFO bi;
BITMAPINFOHEADER bmi = {sizeof(bi.bmiHeader),iHRes,iVRes,1,24,BI_BITFIELDS,iHRes*iVRes*3,0,0,0,0};

bi.bmiHeader = bmi;

hDC = ::GetDC(hWnd);
::GetClientRect(hWnd,&r);

::SelectObject(hMemDC,hBMP);

::glViewport(0,0,r.right,r.bottom);
::glMatrixMode(GL_PROJECTION);
::glLoadIdentity();
::glOrtho(0.0, r.right - 1.0, 0.0, r.bottom - 1.0, -1.0, 1.0);

::glRasterPos2i(0,0);

// Main message loop:
do {
if(::PeekMessage(&msg,NULL,0,0,PM_REMOVE)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
::BitBlt(hMemDC,0,0,iHRes,iVRes ,hDesktopDC,0,0,SRCCOPY);
::GetDIBits(hMemDC,hBMP,0,iVRes,bySrcData,&bi,DIB_RGB_COLORS);

::memcpy(byDestData,bySrcData,iHRes*iVRes*4);

// ::Laplace(bySrcData,byDestData,iHRes,iVRes);
::glPixelZoom((double)r.right/(double)iHRes,(double)r.bottom/(double)iVRes);
::glDrawPixels(iHRes,iVRes, GL_RGBA, GL_UNSIGNED_BYTE, byDestData);
::glFlush();
::SwapBuffers(hDC);
} while( msg.message != WM_QUIT);
}


I cleaned more than I thought I would out cause just a plain memcpy yields me nothing :(

GetObject() doesn't work because the bmBits member of the BITMAP struct is NULL..... in case that crossed your mind. Thanks for the help.

(I had modified some of my code just to eliminate potential places where I would mess up... like bits/pixel and other image stuff so my last compilation was with all 32bit stuff and just a memcpy..... I have a grey window. Which means nothing is being copied over. I should see a mini version of my desktop in the window.

Might be my OGL code... switching to modelview and loading the identity after setting up the camera doesn't change any results.)

hoxsiew
December 1st, 2009, 08:58 AM
I don't know about the openGL stuff, but if you want to create a really compatible bitmap, make the following changes:



HWND hWnd;
HDC hDesktopDC = ::GetDC(::GetDesktopWindow());
HDC hMemDC = ::CreateCompatibleDC(hDesktopDC);
int iHRes = ::GetDeviceCaps(hDesktopDC,DESKTOPHORZRES),
iVRes = ::GetDeviceCaps(hDesktopDC,DESKTOPVERTRES);
int iBpp = ::GetDeviceCaps(hDesktopDC,BITSPIXEL);
HBITMAP hBMP = CreateBitmap(iHRes,iVRes,1,iBpp,NULL);
//HBITMAP hBMP = CreateCompatibleBitmap(hDesktopDC,iHRes,iVRes);


Now you should have a valid pointer to bmBits.

robione
December 1st, 2009, 12:09 PM
Pointer remains NULL. It remains NULL even if I do:


int iHRes = ::GetDeviceCaps(hDesktopDC,DESKTOPHORZRES),
iVRes = ::GetDeviceCaps(hDesktopDC,DESKTOPVERTRES),
iBPP = ::GetDeviceCaps(hDesktopDC,BITSPIXEL),
iClrPlane = ::GetDeviceCaps(hDesktopDC,PLANES);
//HBITMAP hBMP = CreateCompatibleBitmap(hDesktopDC,iHRes,iVRes);
unsigned int iAlloc = iHRes*iVRes*(iBPP >> 3)*iClrPlane;

BYTE *bySrcData = new unsigned char[iAlloc],
*byDestData = new unsigned char[iAlloc];
HBITMAP hBMP = CreateBitmap(iHRes,iVRes,iClrPlane,iBPP,bySrcData);


hBMP does have a valid handle.

hoxsiew
December 1st, 2009, 02:33 PM
If hBMP is invalid, call GetLastError() to see why it failed.

Otherwise, I don't have an answer for you. Maybe OpenGL does something (I'm completely unfamiliar with it) or maybe the MFC CBitmap class that I'm used to dealing with does it different:


BITMAP bm;
CBitmap bmp;

bmp.CreateBitmap(iWidth,iHeight,1,iBpp,NULL);
bmp.GetBitmap(&bm);

// bm.bmBits is valid

hoxsiew
December 1st, 2009, 03:58 PM
What about ::GetBitmapBits()?

robione
December 1st, 2009, 08:09 PM
I'll try that out. I took a screenie and saved it in paint. The OGL code is fine, for the most part. I think the R and B values are flipped, aside from that the image looks fine.

Your recommendation is where a big spot is for wasted time..... at least from my assumptions. Assumptions being the key word. Doing BitBlt writes the desktop to the Bitmap...... then I'm calling GetDIBits to copy the bits into into an array.... copy of a copy.

There is no function that just returns a const * to the data is there?

TBH I'll be happy to get it working then I can worry about speed :) Thanks for your help in this.

robione
December 1st, 2009, 09:21 PM
I got it sorta working :D..... Googling "using Bitblt" gave me post from here that helped out. I'll post my code as soon as I figure out all the image stuff so it actually looks like my screen :)

robione
December 1st, 2009, 09:49 PM
The article I was reading is at (http://www.codeguru.com/forum/showthread.php?t=412541)

Here's the working code:

int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
HWND hWnd;
HDC hDesktopDC = ::GetDC(::GetDesktopWindow());
HDC hMemDC = ::CreateCompatibleDC(hDesktopDC);
int iHRes = ::GetDeviceCaps(hDesktopDC,DESKTOPHORZRES),
iVRes = ::GetDeviceCaps(hDesktopDC,DESKTOPVERTRES),
iBPP = ::GetDeviceCaps(hDesktopDC,BITSPIXEL),
iClrPlane = ::GetDeviceCaps(hDesktopDC,PLANES);
unsigned int iAlloc = iHRes*iVRes*(iBPP >> 3)*iClrPlane;

BYTE *bySrcData,
*byDestData = new unsigned char[iAlloc];

HBITMAP hBMP;
HDC hDC;
HGLRC hRC;
MSG msg;
BITMAPINFO bi;
RECT r;

::memset(&bi.bmiHeader,0,sizeof(bi.bmiHeader));

bi.bmiHeader.biSize = sizeof(bi.bmiHeader);
bi.bmiHeader.biWidth = iHRes;
bi.bmiHeader.biHeight = iVRes;
bi.bmiHeader.biPlanes = iClrPlane;
bi.bmiHeader.biBitCount = iBPP;

::Initialize(hInstance,hWnd,nCmdShow);
::InitOGL(hWnd,hDC,hRC,r);

::glPixelZoom((float)r.right/(float)iHRes,(float)r.bottom/(float)iVRes);

hBMP = ::CreateDIBSection(hMemDC,&bi,DIB_RGB_COLORS,(void **)&bySrcData,NULL,0);
::SelectObject(hMemDC,hBMP);

do {
if(::PeekMessage(&msg,NULL,0,0,PM_REMOVE)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
::BitBlt(hMemDC,0,0,iHRes,iVRes ,hDesktopDC,0,0,SRCCOPY);

::memcpy(byDestData,bySrcData,iAlloc);

// ::Laplace(bySrcData,byDestData,iHRes,iVRes);
::glDrawPixels(iHRes,iVRes, GL_RGBA/*GL_LUMINANCE*/, GL_UNSIGNED_BYTE, byDestData);
::glFlush();
::SwapBuffers(hDC);
} while( msg.message != WM_QUIT);

::wglMakeCurrent(NULL,NULL);
::ReleaseDC(hWnd,hDC);
::wglDeleteContext(hRC);

::SelectObject(hMemDC,NULL);
::ReleaseDC(NULL,hMemDC);
::DeleteObject(hBMP);

delete[] byDestData;

return 0;
}