2007-02-19

Screen Capture with wxWindows

For reasons that will become clear when I post some more software and video, I need to have a general method of capturing any part of the screen and using it as a texture in custom applications.

Google searches for 'screen capture' 'capture screen' or 'screen grab' turn up a lot of other peoples shareware screen grab stuff, and lots of mailing lists where screen captures are provided of one thing or another but it's hardly ever about the code that did the work. How to find some usable code to do what I needed? The answer turned out to be sourceforge, where I found sseditor:

wxWin32ScreenShot.cpp

I was hoping there would have been a platform independent method where you just ask the video card to give you all the pixels, but it turns out to be windowing platform specific. This instance used Windows and wxWindows. I compiled wxWindows 2.8.0 for Cygwin and saw that the examples work. I then copied the screengrab code out of sseditor and put it in a wxWindows sample called 'drawing'. That worked after a little tweaking.

I then took the code and put it into a custom OSG application. There were lots of problems, and I was worried the wxWindows libraries or just using wxWindows in OSG at all was causing instability. I then ran into a problem where calling 'CreateCompatibleBitmap' failed after exactly 153 calls, but it turned out I needed to call DeleteObject on the HBITMAP before calling CreateCompatible repeatedly, I was using up graphics memory and not freeing it.

Also, I had to call wxInitialize() and wxUninitialize() at the beginning and end of my program.



void wxScreenCapture(wxDC& dc)
{
int sizeX = 0;
int sizeY = 0;
sizeX = tex_width; //GetSystemMetrics(SM_CXSCREEN);
sizeY = tex_height; //GetSystemMetrics(SM_CYSCREEN);

compat_counter++;

bmp->SetHeight(sizeY);
bmp->SetWidth(sizeX);


HDC mainWinDC = GetDC(GetDesktopWindow());
HDC memDC = CreateCompatibleDC(mainWinDC);

if (bitmap != NULL) DeleteObject(bitmap);

bitmap = CreateCompatibleBitmap(mainWinDC,tex_width,tex_height);

if (bitmap == NULL) {
std::cerr << "CreateCompatibleBitmap failed at " <<
compat_counter << ", " <<
mainWinDC << " " << tex_width << " " <<
tex_height << std::endl;
exit(1);
return;
}

HGDIOBJ hOld = SelectObject(memDC,bitmap);
BitBlt(memDC, 0, 0,sizeX,sizeY, mainWinDC, 20, 20, SRCCOPY);
SelectObject(memDC, hOld);
DeleteDC(memDC);
ReleaseDC(GetDesktopWindow(), mainWinDC);
bmp->SetHBITMAP((WXHBITMAP)bitmap);
if (bmp->Ok() ) {
//dc.DrawText( _T("BMP ok"), 30, 20 );

} else {
//dc.DrawText( _T("BMP not ok"), 30, 20 );
std::cerr << "bmp not ok" << std::endl;
return;
}

if (savewximage) {
bmp->SaveFile( wxT("/cygdrive/b/text.bmp"), wxBITMAP_TYPE_BMP);
}



My other problem was that I didn't know how to translate between the wxBitmap format of wxWindows and the texture format of the osg::Image. The first way I made it work was by writing the bitmap to disk using a wxWindows function then loading that bmp to a texturing in OSG- this worked but was hard on the disk drive for high rate screen capturing, but writing to a ramdisk can fix that.

But the seeming right way to do it:


/// get image from desktop in wxBitmap format,
// convert it to osg::Image format
wxAlphaPixelData rawbmp(*bmp, wxPoint(0,0),
wxSize(tex_width, tex_height));
wxAlphaPixelData::Iterator p(rawbmp);

image->allocateImage(tex_width, tex_height,
1, GL_RGBA, GL_FLOAT);

/// image is an osg::Image
float* img_data = (float*)image->data();
for (unsigned i = 0; (i < tex_height); i++) {
for (unsigned j = 0; (j < tex_width); j++) {

int ind = i*tex_width + j;

bool flip = ((i > tex_height/4-1) && (i < 3*tex_height/4)
&& (j > tex_width/4-1) && (j < 3*tex_width/4));

img_data[ind*4] = flip ? 1.0 - p.Red()/255.0 : p.Red()/255.0;

img_data[ind*4+1] = flip ? 1.0 - p.Green()/255.0 : p.Green()/255.0;

img_data[ind*4+2] = flip ? 1.0 - p.Blue()/255.0 : p.Blue()/255.0;

img_data[ind*4+3] = 1.0;

p.MoveTo(rawbmp, j, tex_height-1-i);
}
}



The flip code seems really wierd, but it was necessary.

Anyway, after all that I felt pretty proud of myself for hacking a working screencap together over a couple of days, not knowing anything about windowing toolkits before that (I've always known enough about them to try and avoid them like the plague, there's no uglier code than window gui widget code).

The only libraries needed for wxWindows are -lwx_base-2.8 and -lwx_msw_core-2.8, though there will be warnings about other wx libs getting auto imported.

1 comment:

Jon said...

wxSize screenSize = wxGetDisplaySize();
wxBitmap bitmap(screenSize.x, screenSize.y);
wxScreenDC dc;
wxMemoryDC memDC;
memDC.SelectObject(bitmap);
memDC.Blit(0, 0, screenSize.x, screenSize.y, &dc, 0, 0);
memDC.SelectObject(wxNullBitmap);
wxString fname = wxFileName::CreateTempFileName(wxT("screenshot"));
bitmap.SaveFile(fname, wxBITMAP_TYPE_PNG);