Text drawn with DrawText() on certain background colors (when SetBkMode is TRANSPARENT) is unacceptably blotchy: How to overcome? - winapi

In a legacy MFC application, I am attempting to draw deep red (255, 26, 26) on a 'Carbon' theme background (97, 107, 136) in a dialog. Although I am using BCGSoft's MFC library to wrap the code that draws the text, the essential code that performs the drawing of the text is the following:
// CODE THAT DRAWS THE TEXT
// Copied from BCGSoft's 'BCGPStatic.cpp' text-drawing routine
// clrText is RGB(255, 26, 26)
COLORREF clrTextOld = pDC->SetTextColor (clrText);
// background is RGB(97, 107, 136)
pDC->SetBkMode (TRANSPARENT);
pDC->DrawText (strText, rectClient, uiDTFlags);
Problem is: The text displayed is unacceptably blotchy:
BLOTCHY TEXT
If I change the color (and nothing else) to a lighter red (255, 164, 164), the text displays just fine:
GOOD TEXT
Because the font is likely of relevance, here is the code that sets the FONT:
// SETTING THE FONT
CFont * currentFont = staticCtl.GetFont();
LOGFONT lf;
memset(&lf, 0, sizeof(LOGFONT));
currentFont->GetLogFont(&lf);
lf.lfHeight = 18;
lf.lfWeight = FW_BOLD;
// I have ALSO TRIED:
// CLEARTYPE_QUALITY
// ANTIALIASED_QUALITY
// NONANTIALIASED_QUALITY
// PROOF_QUALITY
// ...and just leaving it unset
lf.lfQuality = CLEARTYPE_NATURAL_QUALITY;
m_staticFontRed.CreateFontIndirect(&lf);
staticCtl.SetFont(&m_staticFontRed);
// The following are specific to BCGSoft
staticCtl.m_hFont = (HFONT)(m_staticFontRed.GetSafeHandle());
staticCtl.m_clrText = RGB(255, 164, 164);
I have researched this extensively and attempted as many 'workarounds' as I can think of. Specifically:
Use TextOut instead of DrawText
Set SetBkMode(OPAQUE) and simply fill the background with FillRect before drawing the text
Remove FW_BOLD from the font
As noted in the code snippet, attempt different 'qualities' in the LOGFONT (NONANTIALIASED_QUALITY, etc.)
What can I do, using MFC / WinAPI (in C++, obviously) to get my choice of color for both text and background to work without the text being unacceptably blotchy?
ADDENDUM
Based on #BarmakShemirani's comment, I zoomed in on the pixels and found that they are RED (after taking a screenshot as a bitmap). Therefore, for certain it would seem, those pixels are being generated as RED on the monitor and simply not being perceived by the eye that way).
Here is a picture of the zoomed-in version:
...The red pixels are present (blotchiness gone).
I now suspect that this is an issue with the MONITOR, and not with the way the pixels are being drawn on-screen or with the human eye.
Thanks to #BarmakShemirani!
ADDENDUM 2
I use my monitor in portrait mode, which is atypical. I just rotated the monitor to landscape and the very same screenshot in my question is significantly less blotchy. This leads me to believe this is mostly a MONITOR issue and not an OS or 'human eye' issue.
...I wonder if some monitors handle this situation better than others, or is this 'built in' to most monitors for some (good) reason?

Related

Maximize Window to Have Client Area Just Fill Screen Issue (Windows 10, Win32 DLL)

First, if this isn't a great way to do this, PLEASE suggest better. I can't seem to find an exact solution to what must be a common desire (see post title).
My approach was to trap the WM_PAINT message and check whether the window position is maximized. If it is, then run this code:
SetWindowLong(hCDGWnd, GWL_STYLE, 0); // remove all styling
SetWindowPos(hCDGWnd, HWND_TOP, 0, 0, 1280, 720, SWP_SHOWWINDOW); // full screen is 1280 x 720
StretchDIBits(hdc, 0, 0, 1280, 720, CDG_XOFFSET, CDG_YOFFSET, CDG_RENDER_WIDTH, CDG_RENDER_HEIGHT,
bmBits, (LPBITMAPINFO)&bmInfo, DIB_RGB_COLORS, SRCCOPY);
What I want to see is my BITMAP stretched to fill the entire client area, which should just fill the entire screen. Actually it is a section taken out of a larger bitmap. It seems like the window is in fact full screen, but the bitmap is its normal size and not stretched. Curiously, when I was fiddling with the window positioning stuff I had all kinds of attempts leaving the styling but trying to position the title bar and frame offscreen (see below) - the bitmap was appearing stretched just fine during those close but failed attempts. Now I've got the window right, suddenly the bitmap no longer stretches. Is there something about removing the styling that would screw up the StretchDIBits function?
Also, when I attempt leaving the style in place, and use AdjustWindowRect() to have my client size be fullscreen, it returns {-3, -26, 1283, 723 } which makes sense - 3 pixel border plus 23 more for title bar on top. But, just to explore things, when I don't even test for maximized state, and just make the window have x = -3, y = -26, cx = 1286, cy = 749, then almost everything is fine except the bottom of the window is shy of fullscreen by about 4 pixels. When I make the window height much bigger - say 760 - IT STAYS THE SAME HEIGHT!? I so confused. If try this maneuver only when maximized, it seems like windows ignores my attempts to have the title bar off the top of the screen.

How do I force a repaint of only the background so a control doesn't repaint over it again?

This question was generated by a response to another question: Common Controls on a Transparent Window?.
Apparently, there is a way to only paint the background without a control painting itself again This would solve the problem of having common control buttons on a transparent background.
So my question is, how do I paint only the background around a common control after the common control has painted itself?
About how to redraw visible borders.
The rounded rectangle drawn by RoundRect is used as representative
The FrameRgn function draws a border around the specified region by using the specified brush.
Simple code demonstration:
HRGN hRegion = ::CreateRoundRectRgn (0, 0, ClientWidth, ClientHeight,12,12);
Canvas->Brush->Style = bsSolid;
Canvas->Brush->Color = RGB(96, 96, 96);
::FrameRgn(Canvas->Handle, hRegion, Canvas->Brush->Handle, 2, 2);
::DeleteObject(hRegion); // Don't leak a GDI object
Link you need: Redraw Border

Borderless window. How to add a shadow and remove 1px border?

I need to create a borderless window with specified background color. I know how to remove a non client area and get something like this:
It's cool but not truly what I want. If you take a closer look at any aero window - there's a shadow around it (actually this is not a shadow but some glow). I found somewhere that I can use this code to add a shadow:
const MARGINS shadow_on = { 1, 1, 1, 1 };
DwmExtendFrameIntoClientArea(hwnd, &shadow_on);
It's almost do it's job (thought this is absolutely not clear to me -
documentation says nothing about relationship of shadow and this function). Almost. There's a thin border appeared around the window. It looks like it's semitransparent and it breaks the look and feel of the window:
I know that it's possible - the visual studio even change the color of this border somehow!
Update: as IInspectable noticed in comments I can use negative margins in DwmExtendFrameIntoClientArea(). I set -1 value and got this result:
As you can see - it's even weirder. I tried to fill a background with color, but without luck.
To remove one pixel border after calling this function:
const MARGINS shadow_on = { 1, 1, 1, 1 };
DwmExtendFrameIntoClientArea(hwnd, &shadow_on);
You need to override WndProc WM_NCCALCSIZE message, and return 0 as the result.
Also you need to create window using WS_CAPTION style. (On Windows XP this code won't produce rectangular window, but there is no shadow on WinXP, so on Windows XP you should fallback to WS_POPUP window style)
By the way, to add shadow it is enough to use this margins:
const MARGINS shadow_on = { 1, 0, 0, 0 };
Here is clean windows API code example how to create such window, it is written on Delphi: https://stackoverflow.com/a/44489430/877099

How do I extend nonclient area into a window thinner than 16 pixels?

I'm working on a project that requires me to draw a thin line horizontally across the screen and drag it vertically. To add a bit of flair I'm trying to add the DWM glass effect to the window so that it will match window boders in Windows 7 and 8 (though I know I won't get transparency in Windows 8).
I've got the line drawn by creating a child window with the non client area extended into the client area using the sheet of glass trick with DwmExtendFrameIntoClientArea like this:
MARGINS margins = { -1, -1, -1, -1 };
DwmEnableComposition(DWM_EC_ENABLECOMPOSITION);
DwmExtendFrameIntoClientArea(DIV_HWND, &margins);
And I create the window like this:
HWND DIV_HWND = CreateWindow(DIV_NAME,
NULL,
WS_VISIBLE,
0, 0, mon_info.rcWork.right, mon_info.rcWork.top + 3,
top->hwnd, NULL,
hInstance, NULL);
And the window class like this:
const wchar_t DIV_NAME[] = L"DIV";
WNDCLASS DIV = {};
DIV.lpfnWndProc = DIV_PROC;
DIV.hInstance = hInstance;
DIV.lpszClassName = DIV_NAME;
DIV.hCursor = LoadCursor(NULL, IDC_SIZEALL);
RegisterClass(&DIV);
I've gotten the window down to a width of 16 pixels with all the nice borders and drop shadow effects I expect to see around a windows border by handling the WM_GETMINMAXINFO message and returning 2 as the ptMinTrackSize.x and y but I can't seem to get it smaller than that.
I can get a "glassy" window by using DwmEnableBlurBehindWindow on a region I set with SetWindowRgn but the color is that of a background window even when it is in focus. Additionally I miss the slight drop shadow and border.
It seems like I might be hitting the size restriction due to border sizes since each border is 8 pixels wide.
Any help is greatly appreciated!
I've found my answer so I'll leave it here on the off chance someone else needs it. All I had to do was call DwmSetWindowAttribute(hwmd, DWMWA_NCRENDERING_POLICY, DWMNCRP_ENABLED, sizeof(int)); to ensure the DWM rendering policy was not based on the window style. This allowed me to use a WS_POPUP window to get the size I wanted with a nicely glass filled interior.
There are a few bugs here:
There is a strange shadow along the top and bottom if the Y dimension of the window is smaller than 16px.
On Vista\7 the rounded window edges give the window a strange oval-like shape.

Win32: How to make drop shadow honor non-rectangular Layered window?

i've created a layered window by adding the the WS_EX_LAYERED extended style:
wndClass.ExStyle = wndClass.ExStyle | WS_EX_LAYERED;
Windows will use black as the chroma key color value. i'm going to leave a large border of black to make the problem obvious:
After the window is constructed, i tell it to use black as a chroma-key color:
SetLayeredWindowAttributes(hwnd, 0x00000000, 255, LWA_COLORKEY);
Now the popup layered window appears partially transparent:
The problem is the final step. i want to use CS_DROPSHADOW class style, available since Windows XP, to create a drop-shadow:
wndClass.Style = wndClass.Style | CS_DROPSHADOW;
The drop shadow appears, but the shadow surrounds the original rectangular window, and doesn't take into account the window's transparency provided by the layered window:
Does anyone know what magical option i've missed somewhere that will make the drop shadow honor the non-rectangular layered window?
Another example of where this issue appears is when you don't include the 6px padding/margin. The hint window as drawn by Windows® themes is non-rectangular. This leaves a small visible gap where the window is transparent, but the drop shadow does not appear:
Microsoft has managed to make it work, as you can see from this hint from Internet Explorer:
Looking closer at a Windows tooltips class hint window. Using SpyXX - i can get its window rect, and class styles:
SpyXX says:
Rectangle: (440, 229)-(544, 249), 104x20
Restored Rect: (440, 229)-(544, 249), 104x20
Client Rect: (0, 0)-(104, 20), 104x20
So everything points to the window itself being 104x20 pixels, with the drop shadow outside the window itself. (Which is consistent with CS_DROPSHADOW.)
Next i can look at the styles of the tooltips window class:
Windows Styles: 94000001
WS_POPUP 80000000
WS_VISIBLE 10000000
WS_CLIPSIBLINGS 4000000
TTS_ALWAYSTIP 1
Extended Styles: 00080088
WS_EX_LAYERED 80000
WS_EX_TOOLWIN 80
WS_EX_TOPMOST 8
Interestingly, it doesn't use CS_SAVEBITS (0x800); which is useful for small, short-lived, windows.
Nor does it use CS_DROPSHADOW (0x20000). So now i wonder how is it drawing outside its own window?
Note: Transparent layered windows is documented as the preferred technique over regions.
Edit: Layered Windows have been around with Windows 2000. CS_DropShadow was added with XP.
Transparent layered windows is documented as the preferred technique over regions.
However, CS_DROPSHADOW does pay attention to regions. If you crop or otherwise shape your window using a region, the drop-shadow will follow the new outline.
Fortunately, you can use regions with layered windows, and by combining the two get the effect you're looking for.
BTW: tooltips_class32 does use CS_DROPSHADOW - you won't see it in the window styles because it's a class style, not a window style.
Why don't you use LWA_ALPHA and build the shadow into the image?
Edit in reponse to your comment:
A) Doesn't stop you using an alpha channeled PNG for a shadow only. Blt the 2 images together and use as one single image.
B) Its not hard to generate a drop shadow. In the image you posted its black with 3 different alpha values.
C) But it doesn't work does it? ie Time to get creative.
D) Then don't try and get windows to do something it won't do for you.
E) Is entirely irrelevant. Layered windows handle that for you.
I strongly recommend you learn more about layered windows because they CAN help you to your goal.
Edit2: You have the bitmap. Its fairly easy to scan over the image and find which bits will be colour keyed (by identifying the black yourself) and hen modify that to have an alpha of 0 where everything else will have an alpha of 255 (Not: You may have to convert the image to a 32-bit image from a lower colour format, you can do this by creating a new DC and copying the image). This will give you the same effect with LWA_ALPHA as with LWA_COLORKEY. From there its fairly easy to identify the pixel at the edge, where the color changes to (R = 0, G = 0, B = 0, A = 0). You then change that first pixel to have a n alpha of 192, the one blow it to 128 and the one below to 64. You now have an alpha'd gradation below the image that will look like the shadow. You can adjust the alpha to get the effect you want.
CS_DROPSHADOW only works with standard rectangular windows. WS_EX_LAYERED windows don't support everything. They are more of a low-level, self-service method to draw exactly what you want.
To get a drop shadow, you'll have to generate the drop-shadow yourself from the alpha channel in the image.

Resources