Capturing window's screenshot excluding certain HWND's styles (WS_SYSMENU) - c++

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.

Related

Draw semitransparently in invisible layered window

My goal is to have a fullscreen overlaying invisible "canvas" on which I can draw using win32's various drawing functions.
The way I am currently attempting it is this:
WNDCLASSA myclass = { 0 };
myclass.lpfnWndProc = WindowProc3;
myclass.hInstance = GetModuleHandle(0);
myclass.lpszClassName = "MyCanvas";
myclass.hbrBackground = CreateSolidBrush(0xFEEDBEEF);
myclass.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClassA(&myclass);
...
HWND wnd = CreateWindowExA(WS_EX_TOPMOST | WS_EX_LAYERED | WS_EX_TRANSPARENT, "MyCanvas", 0, WS_POPUP | WS_VISIBLE, 0, 0, screen_width, screen_height, 0, 0, GetModuleHandle(0), 0);
SetLayeredWindowAttributes(wnd, 0xFEEDBEEF, 0, LWA_COLORKEY);
Although this serves as a canvas, hours of googling later, I am still unable to draw on it semitransparently.
I have added a screenshot of what my program is currently displaying as I am writing this. What I would like to be able to do is, for example, make the black box in the top right corner (drawn with Rectangle) semitransparent so as to reveal the stackoverflow page content below it.
This is a question I found that I was hopeful about, but the resulting text is just a blended combination of the background color ((COLORREF)0xFEEDBEEF) and text color. Other things I have found have either just made the element fully invisible, done nothing at all, or required some library like MFC. I want to only use win32 functions if at all possible, as I would like to be able to achieve the highest FPS possible.
I do not care if this doesn't work on all Windows versions as long as it does on 7 up to 10.
If you only need transparency for a rectangular area where all pixels either have the same transparency (aka alpha) value or are completely transparent, you can use SetLayeredWindowAttributes() with a combination of alpha value and/or color key.
UpdateLayeredWindow() is the way to go if you need to be able to define transparency per-pixel.
For that you have to create memory DC and select a 32bpp bitmap into it. You may use the buffered paint API to ease the task. Raymond Chen has a blog post with a code sample about that.
You can draw into the memory DC, but you can't use most of GDI API for that, because GDI ignores the alpha channel (transparency). I suggest using GDI+ which allows you to specify the alpha values.
After you have completed drawing into the memory DC, you would call UpdateLayeredWindow() and pass that memory DC as the argument for the hdcSrc parameter to make the result visible on screen.
Illustration of possible effects:
SetLayeredWindowAttributes( hwnd, 0, 176, LWA_ALPHA );
SetLayeredWindowAttributes( hwnd, colorkey, 0, LWA_COLORKEY );
SetLayeredWindowAttributes( hwnd, colorkey, 176, LWA_ALPHA|LWA_COLORKEY );
UpdateLayeredWindow( ... )
Note the antialiased edge of the shape and the transparency gradient in the last example. Things like that are only possible with UpdateLayeredWindow().

SetWindowPos not setting window to the correct size [duplicate]

I'm working on an app that positions windows on the screen in a grid style. When Running this on Windows 10, there is a huge gap between the windows. Further investigation shows that GetWindowRect is returning unexpected values, including an invisible border, but I can't get it to return the real values with the visible border.
1) This thread suggests this is by design and you can "fix" it by linking with winver=6. My environment does not allow this but I've tried changing the PE MajorOperatingSystemVersion and MajorSubsystemVersion to 6 with no affect
2) That same thread also suggests using DwmGetWindowAttribute with DWMWA_EXTENDED_FRAME_BOUNDS to get the real coordinates from DWM, which works, but means changing everywhere that gets the window coordinates. It also doesn't allow the value to be set, leaving us to reverse the process to be able to set the window size.
3) This question suggests it's lack of the DPI awareness in the process. Neither setting the DPI awareness flag in the manifest, or calling SetProcessDpiAwareness had any result.
4) On a whim, I've also tried adding the Windows Vista, 7, 8, 8.1 and 10 compatibility flags, and the Windows themes manifest with no change.
This window is moved to 0x0, 1280x1024, supposedly to fill the entire screen, and when querying the coordinates back, we get the same values.
The window however is actually 14 pixels narrower, to take into account the border on older versions of Windows.
How can I convince Windows to let me work with the real window coordinates?
Windows 10 has thin invisible borders on left, right, and bottom, it is used to grip the mouse for resizing. The borders might look like this: 7,0,7,7 (left, top, right, bottom)
When you call SetWindowPos to put the window at this coordinates:
0, 0, 1280, 1024
The window will pick those exact coordinates, and GetWindowRect will return the same coordinates. But visually, the window appears to be here:
7, 0, 1273, 1017
You can fool the window and tell it to go here instead:
-7, 0, 1287, 1031
To do that, we get Windows 10 border thickness:
RECT rect, frame;
GetWindowRect(hwnd, &rect);
DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frame, sizeof(RECT));
//rect should be `0, 0, 1280, 1024`
//frame should be `7, 0, 1273, 1017`
RECT border;
border.left = frame.left - rect.left;
border.top = frame.top - rect.top;
border.right = rect.right - frame.right;
border.bottom = rect.bottom - frame.bottom;
//border should be `7, 0, 7, 7`
Then offset the rectangle like so:
rect.left -= border.left;
rect.top -= border.top;
rect.right += border.left + border.right;
rect.bottom += border.top + border.bottom;
//new rect should be `-7, 0, 1287, 1031`
Unless there is a simpler solution!
How can I convince Windows to let me work with the real window coordinates?
You are already working with the real coordinates. Windows10 has simply chosen to hide the borders from your eyes. But nonetheless they are still there. Mousing past the edges of the window, your cursor will change to the resizing cursor, meaning that its still actually over the window.
If you want your eyes to match what Windows is telling you, you could try exposing those borders so that they are visible again, using the Aero Lite theme:
http://winaero.com/blog/enable-the-hidden-aero-lite-theme-in-windows-10/
AdjustWindowRectEx (or on Windows 10 and later AdjustWindowRectExForDpi) might be of use. These functions will convert a client rectangle into a window size.
I'm guessing you don't want to overlap the borders though, so this probably isn't a full solution--but it may be part of the solution and may be useful to other people coming across this question.
Here's a quick snippet from my codebase where I've successfully used these to set the window size to get a desired client size, pardon the error handling macros:
DWORD window_style = (DWORD)GetWindowLong(global_context->window, GWL_STYLE);
CHECK_CODE(window_style);
CHECK(window_style != WS_OVERLAPPED); // Required by AdjustWindowRectEx
DWORD window_style_ex = (DWORD)GetWindowLong(global_context->window, GWL_EXSTYLE);
CHECK_CODE(window_style_ex);
// XXX: Use DPI aware version?
RECT requested_size = {};
requested_size.right = width;
requested_size.bottom = height;
AdjustWindowRectEx(
&requested_size,
window_style,
false, // XXX: Why always false here?
window_style_ex
);
UINT set_window_pos_flags = SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER;
CHECK_CODE(SetWindowPos(
global_context->window,
nullptr,
0,
0,
requested_size.right - requested_size.left,
requested_size.bottom - requested_size.top,
set_window_pos_flags
));
There are still two ambiguities in the above use case:
My window does have a menu, but I have to pass in false for the menu param or I get the wrong size out. I'll update this answer with an explanation if I figure out why this is!
I haven't yet read about how Windows handles DPI awareness so I'm not sure when you want to use that function vs the non DPI aware one
You can respond to the WM_NCCALCSIZE message, modify WndProc's default behaviour to remove the invisible border.
As this document and this document explain, when wParam > 0, On request wParam.Rgrc[0] contains the new coordinates of the window and when the procedure returns, Response wParam.Rgrc[0] contains the coordinates of the new client rectangle.
The golang code sample:
case win.WM_NCCALCSIZE:
log.Println("----------------- WM_NCCALCSIZE:", wParam, lParam)
if wParam > 0 {
params := (*win.NCCALCSIZE_PARAMS)(unsafe.Pointer(lParam))
params.Rgrc[0].Top = params.Rgrc[2].Top
params.Rgrc[0].Left = params.Rgrc[0].Left + 1
params.Rgrc[0].Bottom = params.Rgrc[0].Bottom - 1
params.Rgrc[0].Right = params.Rgrc[0].Right - 1
return 0x0300
}

How can I get the pixel of the covered window?

I am using Visual Studio 2008 on Windows XP and studying C++.
I am curious about the getting pixel from desktop.
Can I get the screen pixel with GetPixel function in the case that the window is covered with another window? In the status of my window being covered, I was trying to call the GetPixel:
GetPixel(hdc, x, y);
But this call dosen't retrieve the pixel of the original window but the covering window or other color one.
How can I get the pixel of the covered window?
I add my code as the following ...
VOID GetDisplay(HWND& hwnd, RECT& area, COLORREF (*display)[1000]) {
HDC hdc=GetDC(hwnd);
for(INT x=area.left;x<=area.right;x++) {
for(int y=area.top;y<=area.bottom;y++) {
display[x][y]=GetPixel(hdc, x, y);
}
}
ReleaseDC(hwnd, hdc);
}
...
SetWindowPos(hwnd, HWND_TOPMOST, 300, 300, 500, 350, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE);
SetForegroundWindow(hwnd);
SetActiveWindow(hwnd);
//EnableWindow(hwnd, TRUE);
SetFocus(hwnd);
GetDisplay(hwnd, within_wall, display);//trouble
The problem is causing on the GetPixel in GetDisplay. GetPixel is returning the pixel color of the covering window.
There's no 100% way to do it, as windows are not required to, and generally don't, draw obscured areas. You will not be able to make the desktop a foreground window of course. And focus has no effect on visibility, as someone has mentioned SetFocus. That would be ineffective.
I have seen proposals to use WM_PRINT to access obscured pixels. Again this is not guaranteed, but the desktop window is pretty specific so if you get that to work, I guess it'll be somewhat reliable.
For more on this, see: PrintWindow.
For what it's worth, GetPixel is extremely slow. You'll probably notice a performance problem in your code. You'll get much better performance by using methods like GetDIBits.

C++: Avoid flickering using WM_ERASEBKGND-Message in MFC-Application

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.

Copy contents of one DeviceContext to another DeviceContext

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