I want to take screenshots of a window which has no focus. My function works for some windows but not for all and I don't know why. I tried it to capture the window of Bluestacks App Player and it works perfectly. But for Nox App Player and some other games it doesn't work at all. I just get a black image with the size of the window.
Here is the code so far:
void screenshot_window(HWND handle) {
RECT client_rect = { 0 };
GetClientRect(handle, &client_rect);
int width = client_rect.right - client_rect.left;
int height = client_rect.bottom - client_rect.top;
HDC hdcScreen = GetDC(handle);
HDC hdc = CreateCompatibleDC(hdcScreen);
HBITMAP hbmp = CreateCompatibleBitmap(hdcScreen, width, height);
SelectObject(hdc, hbmp);
BitBlt(hdc, 0, 0, width, height, hdcScreen, 0, 0, SRCCOPY);
BITMAPINFO bmp_info = { 0 };
bmp_info.bmiHeader.biSize = sizeof(bmp_info.bmiHeader);
bmp_info.bmiHeader.biWidth = width;
bmp_info.bmiHeader.biHeight = height;
bmp_info.bmiHeader.biPlanes = 1;
bmp_info.bmiHeader.biBitCount = 24;
bmp_info.bmiHeader.biCompression = BI_RGB;
int bmp_padding = (width * 3) % 4;
if (bmp_padding != 0) bmp_padding = 4 - bmp_padding;
BYTE *bmp_pixels = new BYTE[(width * 3 + bmp_padding) * height];;
GetDIBits(hdc, hbmp, 0, height, bmp_pixels, &bmp_info, DIB_RGB_COLORS);
BITMAPFILEHEADER bmfHeader;
HANDLE bmp_file_handle = CreateFile("TestFile.bmp", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
// Add the size of the headers to the size of the bitmap to get the total file size
DWORD dwSizeofDIB = (width * 3 + bmp_padding) * height + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
//Offset to where the actual bitmap bits start.
bmfHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER);
//Size of the file
bmfHeader.bfSize = dwSizeofDIB;
//bfType must always be BM for Bitmaps
bmfHeader.bfType = 0x4D42; //BM
DWORD dwBytesWritten = 0;
WriteFile(bmp_file_handle, (LPSTR)&bmfHeader, sizeof(BITMAPFILEHEADER), &dwBytesWritten, NULL);
WriteFile(bmp_file_handle, (LPSTR)&bmp_info.bmiHeader, sizeof(BITMAPINFOHEADER), &dwBytesWritten, NULL);
WriteFile(bmp_file_handle, (LPSTR)bmp_pixels, (width * 3 + bmp_padding) * height, &dwBytesWritten, NULL);
//Close the handle for the file that was created
CloseHandle(bmp_file_handle);
DeleteDC(hdc);
DeleteObject(hbmp);
ReleaseDC(NULL, hdcScreen);
delete[] bmp_pixels;
}
This can happen with many applications where the target window is simply a container and is not responsible for paint messages. Standard win32 applications like notepad don't behave as such. But you may run in to this problem with many browsers for example.
You can always take screenshot of desktop window. You can get the screen coordinate of target window, then bitblt that section of target window. Make the following changes to your code:
//GetClientRect(handle, &client_rect);
GetWindowRect(handle, &client_rect);
//HDC hdcScreen = GetDC(handle);
HDC hdcScreen = GetDC(HWND_DESKTOP);
//BitBlt(hdc, 0, 0, width, height, hdcScreen, 0, 0, SRCCOPY);
BitBlt(hdc, 0, 0, width, height, hdcScreen, client_rect.left, client_rect.top, SRCCOPY);
//ReleaseDC(NULL, hdcScreen);
ReleaseDC(HWND_DESKTOP, hdcScreen);
The target window must be top-most visible window on the screen before taking screen shot. For example you can call screenshot_window in this order:
HWND hwnd = FindWindow(0, L"Calculator");
SetForegroundWindow(hwnd);
Sleep(1000);
screenshot_window(hwnd);
Alternatively, you can use Dwm Thumbnail APIs to paint the target window in your own window. But again, you cannot use GetDC(my_hWnd) to access the bitmap from "Dwm Thumbnail" on your window. Again you would have to take screen shot of desktop window using GetDC(HWND_DESKTOP). This time make sure your own window is the top window.
Application must be DPI aware otherwise screen coordinates will not match.
Also there is a resource leak in the original code. GetDC should be cleaned up with ReleaseDC using the same handle, not NULL
HDC hdcScreen = GetDC(handle);
...
//ReleaseDC(NULL, hdcScreen);
ReleaseDC(handle, hdcScreen);
I need do copy HDC content but my code is not working - any ideas why? Everything is well until I'm trying to copy between HDC objects. It seems that bits goes nowhere. I'm new to GDI programming.
I'm not sure how SelectObject should work here.
PAINTSTRUCT ps;
HDC paintDC = BeginPaint(hWnd, &ps);
HDC imageDC = ::CreateCompatibleDC(paintDC);
HDC bufferDC = ::CreateCompatibleDC(paintDC);
BITMAPINFO bitmapInfo;
memset ( &bitmapInfo, 0, sizeof(BITMAPINFOHEADER) );
bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
int scanLines = GetDIBits(imageDC, // handle to DC
m_bitmap, // handle to bitmap
0, // first scan line to set
0, // number of scan lines to copy
NULL, // array for bitmap bits
&bitmapInfo, // bitmap data buffer
DIB_RGB_COLORS ); // RGB or palette index
// Paint the bitmap image.
HBITMAP pOldBitmap = (HBITMAP)SelectObject( imageDC, m_bitmap );
int width = bitmapInfo.bmiHeader.biWidth;
int height = bitmapInfo.bmiHeader.biHeight;
// Copy imageDC to bufferDC
BitBlt(bufferDC, 0, 0,
width, height,
imageDC, 0, 0, SRCCOPY) ;
BitBlt(paintDC, 0, 0,
width, height,
imageDC, 0, 0, SRCCOPY);
SelectObject(imageDC, pOldBitmap);
When you call CreateCompatibleDC function it'll create so called "memory DC" object. Memory DC doesn't have any image data attached to it by default (in fact id does, 1x1 monochrome bitmap, which is not what you want).
You do select a bitmap object for your imageDC, but you don't select any bitmap object for your bufferDC, that's why "bits go nowhere".
Create a HBITMAP object for your bufferDC (you can use CreateCompatibleBitmap for this) and select it for your bufferDC before performing any blits into it.
Remember one thing: DC object is an interface to something you can paint on, it doesn't contain any bitmap data itself.
How do I make it so that this code strictly does the conversion from a Windows ICON to a CBitmap?
The code is incorrectly displaying the new bitmap on screen. :(
This code was acquired from 'someone' on the web. And though it achieves it's goal of converting the ICON, it also displays the icon on screen (upper left hand corner) which it should not be doing.
D'oh!
void CUIHelper::ConvertIconToBitmap2(CBitmap& bmpObj, HICON hIcon)
{
CClientDC clientDC(NULL);
CDC memDC;
memDC.CreateCompatibleDC(&clientDC);
ASSERT(hIcon);
ICONINFO info;
VERIFY(GetIconInfo(hIcon, &info));
BITMAP bmp;
GetObject(info.hbmColor, sizeof(bmp), &bmp);
HBITMAP hBitmap = (HBITMAP)CopyImage(info.hbmColor, IMAGE_BITMAP, 0, 0, 0);
ASSERT(hBitmap);
ASSERT(memDC.GetSafeHdc());
HBITMAP hOldBmp = (HBITMAP)memDC.SelectObject(hBitmap);
clientDC.BitBlt(0, 0, bmp.bmWidth, bmp.bmHeight, &memDC, 0, 0, SRCCOPY);
memDC.SelectObject(hOldBmp);
VERIFY( bmpObj.Attach(hBitmap) );
DeleteObject(info.hbmColor);
DeleteObject(info.hbmMask);
}
I am dumb when it comes to GDI.
clientDC.BitBlt(0, 0, bmp.bmWidth, bmp.bmHeight, &memDC, 0, 0, SRCCOPY);
should be
memDC.BitBlt(0, 0, bmp.bmWidth, bmp.bmHeight, &memDC, 0, 0, SRCCOPY);
I've looked at multiple responses to similar questions from both this site and others, and while I feel like I've gotten closer, I just can't quite get it right. Still, this is probably a super-noobish question.
So I used to only call the WndProc case "WM_Paint" (via InvalidateRect) one every few minutes, so I didn't really notice the leak. Now I've added something that calls it about 5 times a second. In that second my memory usage jumps about 3800k. Yeah, that got noticed...
Here's the code:
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
// Other cases omitted since we skip them due to "case WM_Paint:".
case WM_PAINT:
wndProc_Paint(hwnd);
break;
// Other cases omitted since we skip them due to "break;".
}
return 0;
}
void wndProc_Paint(HWND hwnd)
{
g_hbmBoard = ConvertIplImageToHBITMAP(targetBoardImg); //OpenCV command
BITMAP bm;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps); // <- Breakpoint here while monitoring mem usage shows this is what is adding ~772k per call which never gets released.
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, g_hbmBoard);
GetObject(g_hbmBoard, sizeof(bm), &bm);
BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
hbmOld = (HBITMAP)SelectObject(hdcMem, g_hbmGreenLight);
GetObject(g_hbmGreenLight, sizeof(bm), &bm);
BitBlt(hdc, screenResW - 59, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
hbmOld = (HBITMAP)SelectObject(hdcMem, g_hbmWorkingLight);
GetObject(g_hbmWorkingLight, sizeof(bm), &bm);
BitBlt(hdc, screenResW - 94, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
hbmOld = (HBITMAP)SelectObject(hdcMem, g_hbmWorkingIndicator);
GetObject(g_hbmWorkingIndicator, sizeof(bm), &bm);
BitBlt(hdc, screenResW - 129, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
//DeleteObject(hbmOld);
SelectObject(hdcMem, hbmOld);
DeleteDC(hdcMem);
//SelectObject(hdc, hbmOld);
//ReleaseDC(hwnd, hdc);
//DeleteDC(hdc);
//DeleteObject(hdc);
EndPaint(hwnd, &ps);
}
With the exception of the g_hbmWorkingIndicator segment and the commented out parts, this is what my WM_Paint looked like before it got called 5times/sec (even before I put it into it's own function - which was a separate issue).
The line "HDC hdc = BeginPaint(hwnd, &ps);" is where the memory gets added, and it never gets released. This happens every time we go through the function. So now for the stuff I tried in fixing the issue.
Reading up on similar issues, I assumed that I needed to Release or Delete the DC of hdc. I wasn't (still aren't - yes I've read the MSDN page) sure how SelectObject works with these, so I've tried it with and without ReleaseDC & DeleteDC (&both) for both hdc and hdcMem. I also tried DeleteObject for hdc & hdcMem. None of those had any affect.
DeleteObject(hbmOld) had an effect. It solved the issue. Except it's the wrong solution, because although I don't have the memory climbing out of control, I do have some of the visuals getting replaced with the wrong visuals. g_hbmGreenLight usually gets g_hbmBoard's graphic while g_hbmWorkingLight turns green (g_hbmGreenLight's graphic). Moving "DeleteObject(hbmOld);" to after EndPaint(~) or trying to use a "SelectObject" just changes which objects get replaced by which wrong graphic - also; returns the memory leak.
EDIT: For completeness's sake, I have included the code for ConvertIplImageToHBITMAP(IplImage* image) here. It is entirely possible that this is the culprit.
HBITMAP ConvertIplImageToHBITMAP(IplImage* pImage)
{
IplImage* image = (IplImage*)pImage;
bool imgConverted = false;
if(pImage->nChannels != 3)
{
IplImage* imageCh3 = cvCreateImage(cvGetSize(pImage), 8, 3);
if(pImage->nChannels==1){cvCvtColor(pImage, imageCh3, CV_GRAY2RGB);}
image = imageCh3;
imgConverted = true;
}
int bpp = image->nChannels * 8;
assert(image->width >= 0 && image->height >= 0 && (bpp == 8 || bpp == 24 || bpp == 32));
CvMat dst;
void* dst_ptr = 0;
HBITMAP hbmp = NULL;
unsigned char buffer[sizeof(BITMAPINFO) + 255*sizeof(RGBQUAD)];
BITMAPINFO* bmi = (BITMAPINFO*)buffer;
BITMAPINFOHEADER* bmih = &(bmi->bmiHeader);
ZeroMemory(bmih, sizeof(BITMAPINFOHEADER));
bmih->biSize = sizeof(BITMAPINFOHEADER);
bmih->biWidth = image->width;
bmih->biHeight = image->origin ? abs(image->height) : -abs(image->height);
bmih->biPlanes = 1;
bmih->biBitCount = bpp;
bmih->biCompression = BI_RGB;
if (bpp == 8)
{
RGBQUAD* palette = bmi->bmiColors;
int i;
for (i = 0; i < 256; i++)
{
palette[i].rgbRed = palette[i].rgbGreen = palette[i].rgbBlue = (BYTE)i;
palette[i].rgbReserved = 0;
}
}
hbmp = CreateDIBSection(NULL, bmi, DIB_RGB_COLORS, &dst_ptr, 0, 0);
cvInitMatHeader(&dst, image->height, image->width, CV_8UC3, dst_ptr, (image->width * image->nChannels + 3) & -4);
cvConvertImage(image, &dst, image->origin ? CV_CVTIMG_FLIP : 0);
if(imgConverted)
{cvReleaseImage(&image);}
return hbmp;
}
So, all that said: Help me StackExchange, you're my only hope! ;_;
You are losing the original HBITMAP that is returned by SelectObject(hdcMem, g_hbmBoard), so you are not restoring it correctly before calling DeleteDC(), and thus it gets leaked. You are overwriting the hbmOld variable every time you call SelectObject(), but you are not restoring the current hbmOld value back into dcMem before overwriting hbmOld again. When using SelectObject(), you MUST restore the original object when you are done making changes to the HDC.
Try this instead:
void wndProc_Paint(HWND hwnd)
{
g_hbmBoard = ConvertIplImageToHBITMAP(targetBoardImg); //OpenCV command
BITMAP bm;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hbmOld = (HBITMAP) SelectObject(hdcMem, g_hbmBoard); // save the original HBITMAP
GetObject(g_hbmBoard, sizeof(bm), &bm);
BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, g_hbmGreenLight); // returns g_hbmBoard, no need to save it to hbmpOld
GetObject(g_hbmGreenLight, sizeof(bm), &bm);
BitBlt(hdc, screenResW - 59, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, g_hbmWorkingLight); // returns g_hbmGreenLight, no need to save it to hbmpOld
GetObject(g_hbmWorkingLight, sizeof(bm), &bm);
BitBlt(hdc, screenResW - 94, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, g_hbmWorkingIndicator); // returns g_hbmWorkingLight, no need to save it to hbmpOld
GetObject(g_hbmWorkingIndicator, sizeof(bm), &bm);
BitBlt(hdc, screenResW - 129, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, hbmOld); // restore the original HBITMAP
DeleteDC(hdcMem);
EndPaint(hwnd, &ps);
// who owns g_hbmBoard? ConvertIplImageToHBITMAP() creates
// a new HBITMAP, so you need to free it with DeleteObject()
// before calling ConvertIplImageToHBITMAP() again. It would
// be better to create g_hbmBoard one time outside of WM_PAINT,
// recreate g_hbmBoard only when the source image actually changes,
// and then re-use g_hbmBoard as-is inside of WM_PAINT.
// ConvertIplImageToHBITMAP() really does not belong in WM_PAINT...
//
//DeleteObject(g_hbmBoard);
}
Alternatively, use SaveDC()/RestoreDC() instead:
void wndProc_Paint(HWND hwnd)
{
g_hbmBoard = ConvertIplImageToHBITMAP(targetBoardImg); //OpenCV command
BITMAP bm;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
HDC hdcMem = CreateCompatibleDC(hdc);
int iOldState = SaveDC(hdcMem); // save everything the HDC currently has selected
SelectObject(hdcMem, g_hbmBoard);
GetObject(g_hbmBoard, sizeof(bm), &bm);
BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, g_hbmGreenLight);
GetObject(g_hbmGreenLight, sizeof(bm), &bm);
BitBlt(hdc, screenResW - 59, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, g_hbmWorkingLight);
GetObject(g_hbmWorkingLight, sizeof(bm), &bm);
BitBlt(hdc, screenResW - 94, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, g_hbmWorkingIndicator);
GetObject(g_hbmWorkingIndicator, sizeof(bm), &bm);
BitBlt(hdc, screenResW - 129, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
RestoreDC(hdcMem, iOldState); // restore everything the HDC originally had selected
DeleteDC(hdcMem);
EndPaint(hwnd, &ps);
// who owns g_hbmBoard? ConvertIplImageToHBITMAP() creates
// a new HBITMAP, so you need to free it with DeleteObject()
// before calling ConvertIplImageToHBITMAP() again. It would
// be better to create g_hbmBoard one time outside of WM_PAINT,
// recreate g_hbmBoard only when the source image actually changes,
// and then re-use g_hbmBoard as-is inside of WM_PAINT...
// ConvertIplImageToHBITMAP() really does not belong in WM_PAINT...
//
//DeleteObject(g_hbmBoard);
}
I want to catch a Desktop frame and store it in a HBITMAP struct.
Then, after creating a proper memory device context out of my application main window's device context, I would select the HBITMAP into it and the use StretchBlt to display the bitmap.
But this doesn't work as expected, because it just shows a black frame.
Both hdc and mem_hdc are respectively the device context and memory device context of main window initialized before.
Here's the code:
...
hDC desk_hdc, desk_mem_hdc;
BITMAP bitmap;
HBITMAP hbitmap;
desk_hdc = GetDC(NULL);
hbitmap = CreateCompatibleBitmap(desk_hdc, GetDeviceCaps(desk_hdc, HORZRES), GetDeviceCaps(desk_hdc, VERTRES));
GetObject(hbitmap, sizeof(BITMAP), &bitmap);
SelectObject(mem_hdc, hbitmap);
StretchBlt(hdc, 0, 0, 1024, 768, mem_hdc, 0, 0, bitmap.bmWidth, bitmap.bmHeight, SRCCOPY|CAPTUREBLT|NOMIRRORBITMAP);
...
The source dc of your StretchBlt operation is mem_hdc, which has a compatible uninitialized bitmap. That's why you get a black frame.
If you want to capture the desktop contents, you have to first copy it to the bitmap in your mem_hdc. Just after SelectObject do:
BitBlt( mem_hdc, 0, 0, bitmap.bmWidth, bitmap.bmHeight, desk_hdc, 0, 0, SRCCOPY );