I am trying to capture the screen contents, modify the bits of the grabbed image directly, and then put the result on the clipboard. (Actually, I'm not ultimately interested in the clipboard, but am using it as a testing step.)
I started with the example from one of the answers to this question. However, it uses CreateCompatibleBitmap
, and from what I understand, there is no way to directly access the bits of bitmaps created with that function, so I am trying to use CreateDIBSection
instead. Here is what I have so far:
void GetScreenShot(void)
{
int x1, y1, w, h;
// get screen dimensions
x1 = GetSystemMetrics(SM_XVIRTUALSCREEN);
y1 = GetSystemMetrics(SM_YVIRTUALSCREEN);
w = GetSystemMetrics(SM_CXVIRTUALSCREEN);
h = GetSystemMetrics(SM_CYVIRTUALSCREEN);
// copy screen to bitmap
HDC hScreen = GetDC(NULL);
HDC hDC = CreateCompatibleDC(hScreen);
if( !hDC )
throw 0;
// This works:
//HBITMAP hBitmap = CreateCompatibleBitmap(hScreen, w, h);
BITMAPINFO BitmapInfo;
BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
BitmapInfo.bmiHeader.biWidth = w;
BitmapInfo.bmiHeader.biHeight = h;
BitmapInfo.bmiHeader.biPlanes = 1;
BitmapInfo.bmiHeader.biBitCount = 24; // assumption; ok for our use case
BitmapInfo.bmiHeader.biCompression = BI_RGB;
BitmapInfo.bmiHeader.biSizeImage = ((w * 3 + 3) & ~3) * h;
BitmapInfo.bmiHeader.biXPelsPerMeter = (int)(GetDeviceCaps( hScreen, LOGPIXELSX ) * 39.3701 + 0.5);
BitmapInfo.bmiHeader.biYPelsPerMeter = (int)(GetDeviceCaps( hScreen, LOGPIXELSY ) * 39.3701 + 0.5);
BitmapInfo.bmiHeader.biClrUsed = 0;
BitmapInfo.bmiHeader.biClrImportant = 0;
BitmapInfo.bmiColors[0].rgbBlue = 0;
BitmapInfo.bmiColors[0].rgbGreen = 0;
BitmapInfo.bmiColors[0].rgbRed = 0;
BitmapInfo.bmiColors[0].rgbReserved = 0;
void *pBits;
// This does not work:
HBITMAP hBitmap = CreateDIBSection( hScreen, &BitmapInfo, DIB_RGB_COLORS, &pBits, NULL, 0 );
if( !hBitmap )
throw 0;
HGDIOBJ old_obj = SelectObject(hDC, hBitmap);
if( !old_obj )
throw 0;
if( !BitBlt(hDC, 0, 0, w, h, hScreen, x1, y1, SRCCOPY) )
throw 0;
if( !SelectObject(hDC, old_obj) )
throw 0;
if( !GdiFlush() )
throw 0;
// this is where we would modify the image
// save bitmap to clipboard
if( !OpenClipboard(NULL) )
throw 0;
if( !EmptyClipboard() )
throw 0;
if( !SetClipboardData( CF_BITMAP, hBitmap ) ) // CF_DIB causes the throw
throw 0;
if( !CloseClipboard() )
throw 0;
// clean up
DeleteDC(hDC);
ReleaseDC(NULL, hScreen);
DeleteObject(hBitmap);
}
However, this does not work. All of the calls report success, but the image does not end up on the clipboard.
When I run this in a debugger, I can see what looks like image data at pBits
after the call to BitBlt
, although it's a bit suspicious in that the first bunch of values have R,G,B all the same, but the bottom-left corner of my screen actually has a bluish colour. Anyway, even if the actual bits are wrong, I should get something of an image on the clipboard, but I don't.
I've tried using CF_DIB
as the first argument to SetClipboardData
instead of CF_BITMAP
, but then the call fails.
If I comment out the call to CreateDIBSection
and uncomment the call to CreateCompatibleBitmap
, then it works, but I have no opportunity to modify the image bits directly.
I guess I could capture my DIB section first, modify it, then call CreateCompatibleBitmap
and blit from the DIB section into the "compatible bitmap", but it seems kind of asinine to copy the bits again for no apparent reason.
Why can't I pass my DIB section to SetClipboardData
?
(I must say I hate working with GDI etc. It's generally clear as mud.)