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().
Related
When I bitblt onto my transparent window, a bmp smaller than the client area, it draws a fussy surrounding area instead of transparent like the set colorkey from:
SetLayeredWindowAttributes(hwnd, RGB(0,255,0), 120, LWA_COLORKEY | LWA_ALPHA); //colorkey & win alpha
The blit looks like this:
The candle-like test image makes its own area transparent like it should, but the surrounding client area gets grayed out according to the window-alpha value (120). Any ideas how to fix this? I've tried:
SetBkColor(hdc, crKey); and generating a background with
FillRect(background_hdc, &background_rect, hbrush); and blitting that first
Where neither work.
I have the feeling this should be alot easier than I'm making it out to be, and I still feel something like SetBkColor(hdc,crKey); should do the trick, but it doesnt. Maybe there is a similar command that does that I'm looking for?
Isnt it funny how you solve the problem just after you formulate it properly?
Obviously the hbrBackground in the WNDCLASSEX wc;
defines the background of the window. So by setting the brush background when registering the window like so:
wc.hbrBackground=CreateSolidBrush(RGB(240, 20, 120)); //COLORREF==crKey
it gets crKey cancelled out during the blit along with the bmps crKey color. Thanks.
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'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
}
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.
I work on an MFC app containing OpenGL context.I am new to MFC that is why I am asking it.OpenGL works fine ,but when I want to draw a text above the 3D window using this code inside WindowProc:
case WM_PAINT:
hDC=BeginPaint(window,&paintStr);
GetClientRect(window,&aRect);
SetBkMode(hDC,TRANSPARENT);
DrawText(hDC,L"He He I am a text on top of OpenGL",-1,&aRect,DT_SINGLELINE|DT_CENTER|DT_VCENTER);
EndPaint(window,&paintStr);
return 0;
it is shown beneath the OpenGL context.I can see it only when resizing the window as the OpenGL rendering pauses than.
What you're doing is wrong and also harder than doing it all in OpenGL. To solve the problem of adding text to an OpenGL-drawn window, it's better to just make OpenGL draw the text. You can even use the exact same font you were using in MFC by creating a CFont instance when you handle WM_CREATE, selecting the font into the DC, and calling wglUseFontBitmaps, which will make a series of rasterized bitmaps that you can use with glCallLists. (While you're at it, call GetCharABCWidths and GetTextMetrics to determine the width and height of each glyph, respectively.)
ABC glyphInfo[256]; // for font widths
TEXTMETRIC tm; // for font heights
// create a bitmap font
CFont myFont;
myFont.CreateFont(
16, // nHeight
0, // nWidth
0, // nEscapement
0, // nOrientation
FW_NORMAL, // nWeight
FALSE, // bItalic
FALSE, // bUnderline
0, // cStrikeOut
ANSI_CHARSET, // nCharSet
OUT_DEFAULT_PRECIS, // nOutPrecision
CLIP_DEFAULT_PRECIS, // nClipPrecision
DEFAULT_QUALITY, // nQuality
DEFAULT_PITCH | FF_SWISS, // nPitchAndFamily
_T("Arial") // lpszFacename
);
// change the current font in the DC
CDC* pDC = CDC::FromHandle(hdc);
// make the system font the device context's selected font
CFont *pOldFont = (CFont *)pDC->SelectObject (&myFont);
// the display list numbering starts at 1000, an arbitrary choice
wglUseFontBitmaps (hdc, 0, 255, 1000);
VERIFY( GetCharABCWidths (hdc, 0, 255, &glyphInfo[0]) );
pDC->GetTextMetrics(&tm);
if(pOldFont)
pDC->SelectObject(pOldFont);
myFont.DeleteObject();
Then when you handle WM_PAINT, reset your matrices and use glRasterPos2d to put the text where you need it to go. I suggest calculating the exact width of your string using code similar to the one below if you want it to be horizontally centered.
// indicate start of glyph display lists
glListBase (1000);
CRect r;
GetWindowRect(r);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
gluOrtho2D(0, r.Width(), 0, r.Height());
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
CString formattedString;
formattedString.Format("Pi is about %1.2f", 3.1415);
int stringWidth=0; // pixels
for(int j=0; j < formattedString.GetLength(); ++j)
stringWidth += glyphInfo[ formattedString.GetAt(j) ].abcA + glyphInfo[ formattedString.GetAt(j) ].abcB + glyphInfo[ formattedString.GetAt(j) ].abcC;
double textXPosition, textYPosition;
textXPosition = r.Width()/2-stringWidth/2; // horizontally centered
textYPosition = r.Height()/2-tm.tmHeight/2; // vertically centered
glRasterPos2d(textXPosition,textYPosition);
// this is what actually draws the text (as a series of rasterized bitmaps)
glCallLists (formattedString.GetLength(), GL_UNSIGNED_BYTE, (LPCSTR)formattedString);
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
While the setup is annoying, you only have to do it once, and I think it's less frustrating than dealing with GDI. Mixing GDI and OpenGL is really asking for trouble, and OpenGL does a very good job of displaying text -- you get sub-pixel accuracy for free, among other benefits.
Edit: In response to your request for including GUI elements, I will assume that you meant that you want to have both OpenGL-drawn windows and also standard Windows controls (edit boxes, check boxes, buttons, list controls, etc.) inside the same parent window. I will also assume that you intend OpenGL to draw only part of the window, not the background of the window.
Since you said you're using MFC, I suggest that you create a dialog window, add all of your standard Windows controls to it, and then add in a CWnd-derived class where you handle WM_PAINT. Use the resource editor to move the control to where you want it. Effectively, you're making an owner-draw custom control where OpenGL is doing the drawing. So OpenGL will draw that window, and the standard MFC classes (CEdit, CButton, etc.) will draw themselves. This works well in my experience, and it's really not much different from what GDI does in an owner-draw control.
What if instead you want OpenGL to draw the background of the window, and you want standard Windows controls to appear on top of it? I don't think this is a great idea, but you can handle WM_PAINT and WM_ERASE for your CDialog-derived class. In WM_ERASE, call OpenGL to draw your 3D content, which will be overwritten by the standard Windows controls when WM_PAINT is called. Alternatively in WM_PAINT you could call OpenGL before calling CDialog::OnDraw, which would be similar.
Please clarify your statement "I want to add some 2s graphics overlay (like labels ,gui elements)" if you want me to write more.
Looking at your code I assume the OpenGL rendering is called from a timer or as idel loop action. Naturally OpenGL execution will probably contain some clearing, thus taking anything else drawn with it.
Mixing GDI text drawing with OpenGL is not recommended, but can be done. But of course you then need to include that code into the OpenGL drawing function, too, placing all GDI operations after the buffer swap.