in the OnDraw()-Method i create a Bitmap and blit it to the output every time the window size changes:
void CmbmView::OnDraw(CDC* pDC)
{
CRect WindowSize;
HDC hdc;
BITMAPINFO pbmi;
HBITMAP hbm;
CBitmap *pBitmap;
CDC MemDC;
void* ppvBits;
GetClientRect(WindowSize);
hdc = CreateDC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;
memset(&pbmi, 0, sizeof(BITMAPINFO));
pbmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pbmi.bmiHeader.biWidth = WindowSize.Width();
pbmi.bmiHeader.biHeight = -WindowSize.Height(); // top down
pbmi.bmiHeader.biPlanes = 1;
pbmi.bmiHeader.biBitCount = 32;
pbmi.bmiHeader.biCompression = BI_RGB;
hbm = CreateDIBSection(hdc, &pbmi, DIB_RGB_COLORS, &ppvBits, NULL, NULL);
pBitmap = CBitmap::FromHandle(hbm);
MemDC.CreateCompatibleDC(pDC);
MemDC.SelectObject(pBitmap);
// "Draw" into ppvBits
GetDocument()->DrawApple(pDC, ppvBits, WindowSize.Width(), WindowSize.Height(), m_MaxIter, m_MaxBetragQuadrat, m_BW);
// Blit it to the output
pDC->BitBlt(0, 0, WindowSize.Width(), WindowSize.Height(), &MemDC, 0, 0, SRCCOPY);
}
But every time the application needs to recreate the bitmap in OnDraw(), the Screen gets white until it blits the bitmap to the screen. How can i use the WM_ERASEBKGND-Message to avoid this flickering?
I don't know with MFC but with the native Windows API you have to process the WM_ERASEBKGND message and simply return TRUE;. This tells the default window procedure that the message is processed hence the window background is erased. As a result the flickering disappears.
Additionally if you're using function InvalidateRect(..) be sure to set parameter bErase to FALSE. Note that the parameter is TRUE by default if not explicitly given.
In an application where rendering the window content took nontrivial amounts of time I took the following steps:
when the screen needs a redraw, blit its content from a bitmap
if the underlying data changes, kick off a thread to render that data into a new bitmap (if that thread is already running, just set a flag)
when the rendering thread finishes, exchange the stored bitmap with the thread result and invalidate the window (if the according flag is set, restart rendering right away)
when the window is resized trigger rendering and stretch-blit the bitmap to the window
when the view is scrolled, blit those parts that are available and trigger rendering
The important benefit is not just that you don't have flickering, but also that the application remains responsive while a thread in it is busy rendering the data into a graphic. In the implementation, apart from the usual multithreading issues, there are a few important things:
Don't run multiple threads in the background at once, as that can degrade performance. If you just stretch the window with the mouse you can easily generate tens of resize messages and you neither want to waste the time nor the amount of memory for that.
Only render the visible parts, as with virtual sizes of a scrollview the bitmap can become really large. In order to make scrolling easier you can add a frame (e.g. 1/5th of the width/height) to keep some additional data available while preparing a new bitmap in the background.
Related
Apparently, Microsoft has changed the way clipping works with Windows update 1809, released in late 2018. Before that update, GetClipBox() returned the full client rectangle of a window, even when it was (partially) offscreen.
After the update, the same function returns a clipped rectangle, only containing the parts that are still onscreen. This leads to the Device Context contents not being updated for the offscreen area, which prevents me from taking screenshots from these windows.
The question is: can I somehow manipulate the clipping region?
I have researched a bit and it seems that the final clipping region is influenced by the window region, the update rectangle, and the system region - as far as I understand the "global clipping region". I've checked the window region with GetWindowRgn() and GetRgnBox(), both return the same values for Windows 1809 and older versions. GetUpdateRect() also returns the full client rectangle, so that cannot be the issue either. I've also tried to hook the BeginPaint() method and see if changing the PAINTSTRUCT.rcPaint does anything, without success.
So what I am left with is trying to adjust the system region, or sometimes called the visible region. However, I have no idea if and how that is possible. MSDN suggests that it's not, but I thought maybe someone does have an idea for a solution!?
EDIT: To make this more clear, I don't think the clipping is done by the application itself, because offscreen screenshots of the same application version work prior to Windows 1809 and don't work with the updated Windows version. Instead, Windows itself seems to clip any offscreen surfaces.
EDIT2: Here's a minimal working code example for taking the screenshot.
// Get the client size.
RECT crect;
GetClientRect(hwnd, &crect);
int width = crect.right - crect.left;
int height = crect.bottom - crect.top;
// Create DC and Bitmap.
HDC windowDC = GetDC(hwnd);
HDC memoryDC = CreateCompatibleDC(windowDC);
BITMAPINFO bitmapInfo;
ZeroMemory(&bitmapInfo, sizeof(BITMAPINFO));
bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bitmapInfo.bmiHeader.biWidth = width;
bitmapInfo.bmiHeader.biHeight = -height;
bitmapInfo.bmiHeader.biPlanes = 1;
bitmapInfo.bmiHeader.biBitCount = 32;
bitmapInfo.bmiHeader.biCompression = BI_RGB;
bitmapInfo.bmiHeader.biSizeImage = width * height * 4;
char* pixels;
HBITMAP bitmap = CreateDIBSection(windowDC, &bitmapInfo, DIB_RGB_COLORS, (void**)&pixels, 0, 0);
HGDIOBJ previousObject = SelectObject(memoryDC, bitmap);
// Take the screenshot. Neither BitBlt nor PrintWindow work.
BitBlt(memoryDC, 0, 0, width, height, windowDC, 0, 0, SRCCOPY);
// ..or..
// PrintWindow(hwnd, memoryDC, PW_CLIENTONLY);
// Save the image.
BITMAPFILEHEADER bitmapFileHeader;
bitmapFileHeader.bfType = 0x4D42;
bitmapFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
std::fstream hFile("./screenshot.bmp", std::ios::out | std::ios::binary);
if(hFile.is_open())
{
hFile.write((char*)&bitmapFileHeader, sizeof(bitmapFileHeader));
hFile.write((char*)&bitmapInfo.bmiHeader, sizeof(bitmapInfo.bmiHeader));
hFile.write(pixels, (((32 * width + 31) & ~31) / 8) * height);
hFile.close();
}
// Free Resources
ReleaseDC(hwnd, windowDC);
SelectObject(memoryDC, previousObject);
DeleteDC(memoryDC);
DeleteObject(bitmap);
You can download a compiled executable from Google Drive here. Usage is Screenshot.exe <HWND>, where HWND is the hex address of the window handle as it is shown in Spy++ for example. It will save a screenshot of the target window in the working directory as screenshot.bmp (make sure you're allowed to write to the directory). The screenshot will work for almost all windows (even if they are hidden behind other windows), but as soon as you partially move the window offscreen, the screenshot will continue to show the old window contents for the offscreen part of the window (resize it while it's offscreen for example, to see the effect). This only happens on Windows 1809, it still shows up-to-date contents on earlier Windows versions.
EDIT3: I did some more research on this. Regarding the AdobeAir application for which the WS_EX_LAYERED style did not work: I found that it uses BitBlt internally do render the back buffer to the window dc. The rendering steps are:
GetDC(hwnd) on the window to obtain hdcWin
CreateCompatibleDC(hdcWin) to create a hdcMem
Call SelectObject(hdcMem, bmp) to select an HBITMAP into hdcMem
BitBlt from hdcMem to hdcWin.
During the BitBlt call, the hdcMem contains valid pixel data even in the offscreen regions, but that data is never copied to the hdcWin.
I looked at the system regions during the BitBlt call. For hdcMem the system region is a NULLREGION, but for the hdcWin the region is always clipped at the screen edges. I also tried to adjust the system region, by replacing all calls to GetDC with GetDCEx(hwnd, hrgn, DCX_CACHE | DCX_INTERSECTRGN) (as mentioned in this article), but that doesn't work and doesn't seem to provide options for extending the region. I really think the secret to solving the problem lies in manipulating the system region for the window dc, but I have no idea how to do that.
If found that the CreateDC function takes a pointer to a DEVMODE struct as the last argument (msdn). That in turn has fields dmPelsWidth, dmPelsHeight and dmPosition. I believe that these make up the system region and maybe if I could manipulate them, the DC would no longer get clipped, but I wasn't able to hook the CreateDC function, yet.
If you have any new ideas based on my new insights, please share them. I'd appreciate any help!
If we take ReactOS as an example, the clipping region is at dc->dclevel.prgnClip and the system region is at dc->prgnVis. When you call BeginPaint on a window, it calls NtUserBeginPaint stub which traps to its kernel counterpart through the win32k SSDT, which calls IntBeginPaint, which passes the window's update region (Window->hrgnUpdate) to UserGetDCEx, which copies this to Dce->hrgnClip and calls DceUpdateVisRgn, which then gets the visible region by calling DceGetVisRgn which calculates the visible region using VIS_ComputeVisibleRegion, which develops a complex region by traversing all child windows, all parent windows and all siblings at each level (a top level window has a parent as the desktop (((PCLIENTINFO)(NtCurrentTeb()->Win32ClientInfo))->pDeskInfo->spwnd) and all top level windows are siblings; the desktop's parent is NULL and removing the parts they cover up – this does not appear to perform any special handling for the desktop window when it gets to it like clipping to the client area, and is treated like any other window in the z order, where only what it is covering is removed). DceGetVisRgn then combines this returned visible region and combines it wil the clipping region Dce->hrgnClip and combines them into RgnVisible using IntGdiCombineRgn(RgnVisible, RgnVisible, RgnClip, RGN_AND), which is then copied into dc->prgnVis using GdiSelectVisRgn(Dce->hDC, RgnVisible). DC is the device context and DCE is the device context entry for the DC in the DC cache. Therefore, the system region of the DC is now the intersection of the visible region and the update region of the window. IntBeginPaint also calls GdiGetClipBox(Ps->hdc, &Ps->rcPaint), which calls REGION_GetRgnBox(pdc->prgnVis, prc) to copy the bound of the region pdc->prgnVis (pdc->prgnVis->rdh.rcBound) to Ps->rcPaint and then GdiGetClipBox calls IntDPtoLP(pdc, (LPPOINT)prc, 2) to convert the bound from physical coordinates to logical coordinates, which DPI-unaware apps use. The paintstruct now contains the smallest logical rectangle that contains the complex intersection of the update region and the visible region.
GetClipRgn calls NtGdiGetRandomRgn, which returns pdc->dclevel.prgnClip when called with CLIPRGN, which is application defined using SetClipRgn
An application-defined clipping region is a clipping region identified by the SelectClipRgn function. It is not a clipping region created when the application calls the BeginPaint function.
There are 2 clipping regions. One is an application defined one created by the application using SelectClipRgn and the pointer is stored in pdc->dclevel.prgnClip, and the other clipping region is system region, after it has been updated to the intersection of the system region and the update region by a BeginPaint call, where it is presented to the application as a logical clipping rectangle in the PAINTSTRUCT.
GetClipBox calls NtGdiGetAppClipBox, which calls GdiGetClipBox, which of course returns the smallest logical rect boundary of the current system region, which may be the visible region if GetDC was used, or it may be the system region intersected with a custom clipping region with GetDCEx, or it may be the system region intersected with the window update region when using BeginPaint. Your issue would imply that the system region, when calculated, is now performing special handling for the desktop window in VIS_ComputeVisibleRegion
To actually access the DC directly, and hence the System region, you'd have to start and interact with a driver to do it from the application.
This seems to be a bug in the relevant versions of Windows which has apparently been fixed in more recent versions.
For anybody sailing through countless google pages, blog posts, SO answers... calling:
SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_LAYERED);
seems to do the trick. This doesn't answer how to extend the clipping region over the area restricted by windows, but allows to capture screen correctly which is pretty much the goal anyway. This also is a solution to many other issues, like the taskbar thumbnail not updating, parts of window filckering with black when dragging or bugs when capturing to video file. MSDN doesn't specifically explain that anywhere.
One more thing worth pointing out is that Discord is able to stream a partially offscreen window WITHOUT modifying any of window's attributes, so there's probably more to that...
GDI is not the best way for doing screenshots, often it can't get even completely visible window.
Few months ago I found Youtube video with DWM hacking, which allows you to take screenshot of any window.
Here are sources. Personally I, didn't try to compile and run it.
I'm trying to periodically compare snapshots of the same window every few of seconds. Even though that technically the window doesn't change, pictures remains the same, i still get notified that something has changed with in the picture, I'm using good old BitBlt function to capture a specific window. and Zlib's CRC32 to compare results.
Here's an example of 2 identical pictures, the sole difference is the windows' caption(untitled paint) color. Whenever the window has the focus it's black, and gray otherwise. Since I'm not the owner of the HWND, Is there a better way of taking a picture without calculating the actual size of the window minus GetSystemMetrices(SM_CYSIZEFRAME / SM_CXSIZE) than changing the style.
My code:
WINDOWPLACEMENT rect;
::GetWindowPlacement(windowDesc.hWnd, &rect);
if (SW_SHOWMINIMIZED == rect.showCmd)
{
return;
}
CImage img;
img.Create(
rect.rcNormalPosition.right - rect.rcNormalPosition.left,
rect.rcNormalPosition.bottom - rect.rcNormalPosition.top,
32);
HWND hWnd = windowDesc.hWnd;
std::shared_ptr<HDC__> spSrcHdc(::GetDC(hWnd), [hWnd](HDC hdc) {::ReleaseDC(hWnd, hdc); });
//::BitBlt(img.GetDC(), 0, 0, img.GetWidth(), img.GetHeight(), spSrcHdc.get(), 0, 0, SRCCOPY);
::PrintWindow(hWnd, img.GetDC(), 0x2);
BITMAP bmp = { 0 };
if (!::GetObject((HBITMAP)img, sizeof(BITMAP), &bmp))
{
throw std::exception("Failed to retrieve raw bmp buffer");
}
unsigned long ulbmpCRC = crc32(0,
(BYTE*)bmp.bmBits, bmp.bmWidthBytes * bmp.bmHeight);
if (0 != ulbmpCRC && ulbmpCRC == windowDesc.crc)
{
}
And another issue i failed to solve is when textboxes are present. The very fact that i have the cursor flickering, it generates different CRC32 values. yet again, can i use BitBlt with the ability to ignore the phenomenon?
To determine the client area of the target window, you can send it a WM_NCCALCSIZE message. This should enable you to determine the size of the caption bar reliably for most applications, something like:
RECT r;
GetWindowRect (hTargetWnd, &r);
SendMessage (hTargetWnd, WM_NCCALCSIZE, FALSE, (LPARAM) &r);
As for the flashing caret problem, you could try remembering the checksums for the last two different screens you have seen. Then you have a chance of working out when the window is, in effect, switching back and forth between two states.
That, and more sophisticated comparison logic (such as walking the list of child windows to look for edit controls to see if any changes are confined to the contents of such controls), should make it possible to achieve most of what you want.
I've never done any GDI programming and despite taking several shots in the dark and searching the documentation I haven't found the correct way to do copy the contents of one DC to another DC.
The code I have at the moment is below. I don't understand why it's not working (the window remains just remains blank after creation).
SIZE srcSize;
// ... Get size of source DC
HDC destDC = ...; // from GetDC(myWindow), myWindow was
// sized before this to properly contain source
HDC sourceDC = ...;
HBITMAP buffer = CreateCompatibleBitmap(sourceDC, srcSize.cx, srcSize.cy);
HGDIOBJ oldObj = SelectObject(destDC, buffer);
BitBlt(destDC, 0, 0, srcSize.cx, srcSize.cy, sourceDC, 0, 0, SRCCOPY);
SelectObject(destDC, oldObj);
DeleteObject(buffer);
//... ReleaseDC()s here
What's the proper way this is done?
The only thing necessary to copy from one DC to another is a BitBlt. Code that works is below.
SIZE srcSize;
// ... Get size of source DC
HDC destDC = ...; // from GetDC(myWindow), myWindow was
// sized before this to properly contain source
HDC sourceDC = ...;
BitBlt(destDC, 0, 0, srcSize.cx, srcSize.cy, sourceDC, 0, 0, SRCCOPY);
//... ReleaseDC()s here
It's not very clear to me what you are trying to do. First off, why create the new bitmap and select it into the window (sorry, "client area") DC? All you want is paint/draw the window, isn't it? This is not needed then. The destDC is exactly the window's client area surface.
Does sourceDC really contain anything? For example, does it have a bitmap slected into it?
And of course, you SHOULD process WM_PAINT. If you process this message the window is validated, and you are not required to validate it explicitly. Using GetDC()/ReleaseDC() is called "drawing", as opposed to "painting". In an application I made in the past I had to use both methods, painting (processing WM_PAINT) for responding to resizing, exiting from minimized state and bringing the window to foreground (if previously obscured by another) and drawing, for making certain changes immediately visible (instead of invalidating the window and waiting for the application to nearly enter the idle state first - pls note that WM_PAINT is a low-priority message).
Hope this helps
I am working on a windows application with C++. I load a bmp file to a DC using LoadImage, and it shows up properly. However, when I call DeleteObject, the memory doesn't seem to be freed. (I use windows task manager to track the memory usage)
In the WM_INITDIALOG part I do this:
static HBITMAP hBitmap = 0;
char* tempPath = "tabView.bmp";
hBitmap = (HBITMAP)LoadImage(NULL,
tempPath, // file containing bitmap
IMAGE_BITMAP, // type = bitmap
0, 0, // original size
LR_LOADFROMFILE); // get image from a file
if(hBitmap)
{
SendMessage(GetDlgItem(hwndDlg, IDC_PICTURE),
STM_SETIMAGE, // message to send
(WPARAM)IMAGE_BITMAP, // bitmap type
(LPARAM)hBitmap); // bitmap handle
}
So the picture shows up in the DC, and memory increases. And in a button I do:
int result = DeleteObject(hBitmap);
When I press the button, I checked the result and it's a non-zero value, which is success. But IDC_PICTURE will still show the picture, and memory stays the same. I am wondering if the SendMessage() may increase the ref count on the hBitmap...
So my question is: What is the proper way to clean up?
You didn't mentioned what version of Windows you are using. Anyway, if you read "Important" part of STM_SETIMAGE, you will see next:
With Windows XP, if the bitmap passed in the STM_SETIMAGE message contains pixels with nonzero alpha, the static control takes a copy of the bitmap. This copied bitmap is returned by the next STM_SETIMAGE message. The client code may independently track the bitmaps passed to the static control, but if it does not check and release the bitmaps returned from STM_SETIMAGE messages, the bitmaps are leaked.
Maybe this applies not only for Windows XP, but for later version of Windows. Hope this will help you.
I'm drawing into a WinAPI-Window by using the SetPixel()-Function.
If I scale the window or lose focus (another window is on top) I lose the whole content that I draw into the window.
I just used
RECT rc;
GetClientRect(hwnd, &rc);
RedrawWindow(hwnd, &rc, NULL, RDW_NOERASE | RDW_NOFRAME | RDW_VALIDATE);
which helped to avoid redrawing the content when I move the window but scaling and losing focus still removes the content.
Does anyone have an idea what I missed?
Draw it to a buffer/bitmap and then draw that to your window.
When a window needs to be repainted it will be sent a WM_PAINT message. At this point you must redraw all of the window, or at least all parts of it which are contained within a clipping region. Windows does some buffering and automatic painting, specifically it will repaint parts of a window which are covered by other windows then uncovered. Once the window has been resized though, or (presumably) invalidated, you're on your own.
As #daniel suggested, if painting is an intensive process and you don't want to do it every time a repaint is required, render your content into a bitmap (which in this case will be an off-screen buffer) and BitBlt (copy) it into the window as necessary.
Grab yourself a copy of Charles Petzold's book "Programming Windows" for information about how you should go about painting. If you are writing a WinAPI app but have used SetPixel I'd recommend reading the entirety of the first few chapters to get an idea of how an old-school Windows programme should be structured.
SetPixel is very slow, you cannot improve your program significantly. Create in-memory bitmap and draw it on the window. For example, you can do this using StretchDIBits function, which draws the whole memory area as bitmap to the window, instead of SetPixel.
The most important StretchDIBits parameters are:
CONST VOID *lpBits - memory array (pixels). You need to fill it in memory instead of SetPixel calls.
CONST BITMAPINFO *lpBitsInfo - BITMAPINFO structure which must describe bitmap structure. For example, if lpBits has BGRW structure (4 bytes per pixel), BITMAPINFO must describe true color bitmap.
You need to draw the content into memory and then draw it to the window when you got WM_PAINT message. There is no way to avoid using memory buffer because window device context does not save what you draw.
Create a DIB surface and draw into it instead. Then redraw the bitmap when redrawing a window.
You're trying to draw with a pre-Windows way in Windows. ;)