Dynamic transparency geometry in MFC - c++

I'm creating a new-UI walkthrough for my MFC application and want to highlight certain controls as the walkthrough proceeds. Specifically, I want to darken the whole window except the control I'm emphasizing.
I tried creating a partly-transparent black overlay using SetLayeredWindowAttributes, but this doesn't let me set a sub-area completely transparent. UpdateLayeredWindow can do this, but I'm not eager to create a BMP/PNG file for every control I need to highlight.
Can I create the transparency geometry dynamically? For example, can I draw bitmap transparency from scratch then load it into UpdateLayeredWindow?
I also need to be compatible with Windows 7 (despite its support EOL).
Follow-up:
Trying to paint transparent GDI+ regions, but doesn't work:
void ApplicationDlg::Highlight(const CRect& rect)
{
CRect wndRect;
GetWindowRect(&wndRect);
Gdiplus::Rect wndRectPlus(wndRect.left, wndRect.top, wndRect.Width(), wndRect.Height());
Gdiplus::Region wndRegion(wndRectPlus);
Gdiplus::Rect controlRectPlus(rect.left, rect.top, rect.Width(), rect.Height());
Gdiplus::Region highlightRegion(controlRectPlus);
wndRegion.Exclude(&highlightRegion);
Gdiplus::SolidBrush transparentBrush(Gdiplus::Color(0, 0, 0, 0));
Gdiplus::SolidBrush darkenBrush(Gdiplus::Color(128, 0, 0, 0));
CDC* pDCScreen = m_WalkthroughDlg.GetDC();
HDC hDC = CreateCompatibleDC(pDCScreen->m_hDC);
HBITMAP hBmp = CreateCompatibleBitmap(hDC, wndRect.Width(), wndRect.Height());
HBITMAP hBmpOld = (HBITMAP)SelectObject(hDC, hBmp);
Gdiplus::Graphics graphics(hDC);
graphics.FillRegion(&darkenBrush, &wndRegion);
graphics.FillRegion(&transparentBrush, &highlightRegion);
BLENDFUNCTION blend = {0};
blend.BlendOp = AC_SRC_OVER;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
SIZE sizeWnd = {wndRect.Width(), wndRect.Height()};
POINT ptSrc = {0,0};
m_WalkthroughDlg.UpdateLayeredWindow(pDCScreen, NULL, &sizeWnd, CDC::FromHandle(hDC), &ptSrc, NULL, &blend, ULW_ALPHA); // TODO cleanup FromHandle refs
m_WalkthroughDlg.BringWindowToTop();
SelectObject(hDC, hBmpOld);
DeleteObject(hBmp);
DeleteDC(hDC);
}

You can dynamically create a mask by using CRgn class: https://learn.microsoft.com/en-us/cpp/mfc/reference/crgn-class?view=vs-2019
It allows you to combine regions (if you need to highlight more than one area). You could then use FillRgn function to update the hdcSrc DC used in UpdateLayeredWindow.
Alternatively, if your highlights are rectangular, you could just draw rectangles on that hdcSrc.

Related

unable to FillRect with transparent brush when double buffering

As the title says I'm unable to FillRect bitmap to be transparent. I know when creating the bitmap it is not monochrome as gray brush works fine but I have no way (that I'm aware of) to check if it is colored or grayscale. I also am aware that by default the bitmap is black hence why I'm trying to change it to transparent. I am also aware that I'm likely not cleaning up the dc's correctly however that is not the main issue. I'm trying to solve the black background by making it transparent.
#include<windows.h>
#include<iostream>
int main()
{
// Init DC
HWND Wnd = GetDesktopWindow();//GetConsoleWindow();
HDC ScreenDC = GetDC(Wnd);
// Init Rectangle
RECT ClientRect;
GetClientRect(Wnd, &ClientRect);
// Init Double Buffer
HDC MemDC = CreateCompatibleDC(ScreenDC);
HBITMAP MemBM = CreateCompatibleBitmap(ScreenDC, ClientRect.right - ClientRect.left, ClientRect.bottom - ClientRect.top);
HBITMAP OldBM = (HBITMAP)SelectObject(MemDC, MemBM);
// Create Brush and Pen
HPEN Pen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
HBRUSH ClearBrush = (HBRUSH)GetStockObject(GRAY_BRUSH);
// Set Brush and Pen
SelectObject(MemDC, Pen);
SelectObject(MemDC, ClearBrush);
POINT p;
while(!GetAsyncKeyState(VK_RETURN))
{
// Clear and Draw
GetCursorPos(&p);
FillRect(MemDC, &ClientRect, ClearBrush);
Rectangle(MemDC, p.x, p.y, p.x+20, p.y+20);
BitBlt(ScreenDC, 0, 0, ClientRect.right - ClientRect.left, ClientRect.bottom + ClientRect.left, MemDC, 0, 0, SRCCOPY);
}
SelectObject(MemDC, OldBM);
DeleteObject(ClearBrush);
DeleteObject(Pen);
DeleteObject(OldBM);
DeleteObject(MemBM);
DeleteDC(MemDC);
ReleaseDC(Wnd, ScreenDC);
return 0;
}
I've tried many different ways of setting transparent background to no avail. The end result is a rectangle appearing over the mouse and following it across the screen however the background shouldn't be black I should be able to see other windows.

Windows/C++ -- rendering icon to bitmap with transparency

I am no expert in anything GDI. But I was given some code YEARS ago which has served me decently well. But, it's getting old ... and with new windows10 dark theme, it's showing it's flaws.
I am rendering a menu (in an explorer menu plugin). Here's the snippet of code used to generate the bitmap.
My goal, to convert this code to generate a bitmap with transparency of the icon preserved.
(the result HBITMAP ends up in pItem->m_hBitmap )
HICON hIcon;
if ( (iIndex >= 0) && (ExtractIconEx(iconDLLPath, iIndex, NULL, &hIcon, 1) != 0) )
{
HDC hdc = CreateIC(L"DISPLAY", NULL, NULL, NULL);
HDC hdcMem = CreateCompatibleDC(hdc);
// XP demands 12x12, otherwise use 16x16
int cx = GetSystemMetrics((m_bUseSmallerIcons) ? SM_CXMENUCHECK : SM_CXSMICON);
int cy = GetSystemMetrics((m_bUseSmallerIcons) ? SM_CYMENUCHECK : SM_CYSMICON);
pItem->m_hBitmap = CreateCompatibleBitmap(hdc, cx, cy);
HBITMAP hBmOld = (HBITMAP) SelectObject(hdcMem, pItem->m_hBitmap);
// DC: paint entire mem dc COLOR_MENU so icon looks transparent
// when painted into context menu having this background color
HBRUSH hBrush = CreateSolidBrush(GetSysColor(COLOR_MENU));
RECT rect;
rect.left = 0;
rect.top = 0;
rect.right = cx;
rect.bottom = cy;
FillRect(hdcMem, &rect, hBrush);
DeleteObject(hBrush);
// Draw icon transparently, on top of the background color. Transparent
// areas will be the background color.
DrawIconEx(hdcMem, 0, 0, hIcon, cx, cy, 0, 0, DI_NORMAL);
// Cleanup
SelectObject(hdcMem, hBmOld);
DeleteDC(hdc);
DeleteDC(hdcMem);
DestroyIcon(hIcon);
}
I should remove the which draws the white background, but how do I put down a transparent background? Everything I've tried yields a black background.
* just removing the white "fill"
* SetBkMode(TRANSPARENT)
* using the theme code to get the menu color...
How do I go about making a proper bitmap with transparency?
Ultimately, the answer is that I am doing nothing wrong. This menu is rendered as part of a menu constructed in a shell plugin using IContextMenu.
The answer - implement IContextMenu2. And in doing so, do an owner draw menu item.

When I use DirectWrite to draw text on a GDI hdc, how to set a transparent back ground?

Now I am working on a legacy product which use GDI to draw text in screen. Now I try to use DirectWrite to draw text for better appearance and accurancy of font. I am very curious that has anyone done this before?
I meet a problem that when I use DirectWrite to draw text on a GDI hdc, the background color is always white, I need a transparent background, is it possible? it seems that the SetBkMode is useless
The sample code is as below,
SetBkMode(hdc, TRANSPARENT); //hDC is the target GDI dc
SIZE size = {};
HDC memoryHdc = NULL;
memoryHdc = g_pBitmapRenderTarget->GetMemoryDC();
SetBkMode(memoryHdc, TRANSPARENT);
hr = g_pBitmapRenderTarget->GetSize(&size);
Rectangle(memoryHdc, 0, 0, size1.cx , size1.cy );
if (SUCCEEDED(hr)) {
hr = g_pTextLayout->Draw(NULL, g_pGdiTextRenderer, 0, 0);
}
BitBlt(hdc, x, y, width + 1, height + 1, memoryHdc, 0, 0, SRCCOPY | NOMIRRORBITMAP);
Default (Stock) brush for freshly created GDI device context is white solid brush, which is why you have white rectangle in output. See GetStockObject
GDI doesn't work with transparent images, BitBlt will replace all pixels inside destination rectangle in target DC. You have to copy contents of target DC destination rectangle in memory DC, then draw text and copy result back to achieve desired effect.
SetBkMode(hdc, TRANSPARENT); //hDC is the target GDI dc
SIZE size = {};
HDC memoryHdc = g_pBitmapRenderTarget->GetMemoryDC();
BitBlt(memoryHdc, 0, 0, width+1, height+1, hdc, x, y, SRCCOPY);
if (SUCCEEDED(hr)) {
hr = g_pTextLayout->Draw(NULL, g_pGdiTextRenderer, 0, 0);
}
BitBlt(hdc, x, y, width + 1, height + 1, memoryHdc, 0, 0, SRCCOPY | NOMIRRORBITMAP);
Make sure you use smallest possible update region, since moving large chunks of bitmaps in memory will surely dwindle performance.
If application uses back buffer to draw windows, memory DC of IDWriteBitmapRenderTarget could be used instead of allocating another one - in that case you solve problem of transparent text background automatically.

How to draw text with transparent background using c++/WinAPI?

How to draw text with transparent color using WinAPI?
In usual way I used SetBkMode(hDC, TRANSPARENT), but now I need to use double buffer.
In this way images draws correct, but text draws not correct (with black background).
case WM_PAINT:
{
hDC = BeginPaint(hWnd, &paintStruct);
SetBkMode(hDC, TRANSPARENT);
HDC cDC = CreateCompatibleDC(hDC);
HBITMAP hBmp = CreateCompatibleBitmap(hDC, width, height);
HANDLE hOld = SelectObject(cDC, hBmp);
HFONT hFont = (HFONT)SelectObject(hDC, font);
SetTextColor(cDC, color);
SetBkMode(cDC, TRANSPARENT);
TextOut(cDC, 0, 0, text, wcslen(text));
SelectObject(cDC, hFont);
BitBlt(hDC, 0, 0, width, height, cDC, 0, 0, SRCCOPY);
SelectObject(cDC, hOld);
DeleteObject(hBmp);
DeleteDC(cDC);
EndPaint(hWnd, &paintStruct);
return 0;
}
SetBkMode(dc, TRANSPARENT) should work fine still. Make sure you're using the correct DC handle when drawing to your back buffer.
When you create a bitmap, the color isn't specified. The documentation doesn't state how it's initialized, but solid black (all zeros) seems likely. Since you're drawing the text on the bitmap, the background of the bitmap remains black. You then copy the entire bitmap to the DC and all the pixels come along, the background along with the text.
To fix this you must copy the desired background into the bitmap before you draw the text.

Help with double-buffering

I have created an animation which works fine, but it flicks. I need help with double-buffering since I don't know anything about it.
This is the code in my onPaint():
VOID onPaint(HDC hdc)
{
Graphics graphics(hdc);
Pen pen(Color(255, 0, 0, 255));
graphics.DrawEllipse(&pen, sf , 0, 10, 10);
}
It works fine but with flicker. I tried this code but it didn't work:
VOID onPaint(HDC hdc,HWND hWnd)
{
HDC hDC=GetDC(hWnd);;
HDC memDC = CreateCompatibleDC(hDC);
HBITMAP hMemBmp = CreateCompatibleBitmap(hDC,10,10);
HBITMAP hOldBmp = (HBITMAP)SelectObject(memDC,hMemBmp);
BitBlt(hDC, 0, 0, 10, 10, memDC, 0, 0, SRCCOPY);
Graphics graphics(memDC);
Pen pen(Color(255, 0, 0, 255));
graphics.DrawEllipse(&pen, sf , 0, 10, 10);
// Always select the old bitmap back into the device context
SelectObject(memDC, hOldBmp);
DeleteObject(hMemBmp);
DeleteDC(memDC);
}
It looks like you're just prematurely copying the offscreen DC to the display. Try moving the call to BitBlt down four lines, to make it the last line before you start the clean-up, like so:
VOID onPaint(HDC hdc,HWND hWnd)
{
// this line looks a little odd :
HDC hDC = GetDC(hWnd);
// .. usually the hdc parameter passed to onPaint would already refer to
// the on-screen DC that windows wants updated. Also worth noting is that
// when you use GetDC(), you should have a matching ReleaseDC()
// As a quick test, you might just replace the above line with
// HDC hDC = hdc;
HDC memDC = CreateCompatibleDC(hDC);
HBITMAP hMemBmp = CreateCompatibleBitmap(hDC,10,10);
HBITMAP hOldBmp = (HBITMAP)SelectObject(memDC,hMemBmp);
// draw to the off-screen map ..
Graphics graphics(memDC);
Pen pen(Color(255, 0, 0, 255));
graphics.DrawEllipse(&pen, sf , 0, 10, 10);
// now that you've drawn on the offscreen map, go ahead
// and put it on screen.
BitBlt(hDC, 0, 0, 10, 10, memDC, 0, 0, SRCCOPY);
// Always select the old bitmap back into the device context
SelectObject(memDC, hOldBmp);
DeleteObject(hMemBmp);
DeleteDC(memDC);
}
Another thing about this code, you've passed the constant '10' as the width and height of your off-screen bitmap, as well as using it for the width and height params to the BitBlt() that does the copy. Chances are the window client area being updated is very much larger than that. The 'black square' is a consequence of blitting the 10x10 off-screen map onto the window client area. Instead of hard-coding 10 there, you might try using another GDI function to obtain the dimensions of the on-screen bitmap, or at the very least you could #define width and height values, and use these in the params.
The other thing killing you is probably the 'sf' in the line "graphics.DrawEllipse(&pen, sf , 0, 10, 10)" -- since you've created an incredibly tiny 10x10 map, if the value of 'sf' is anything outside of 0..10, the DrawEllipse() call will place the ellipse entirely outside of the available pixels in your offscreen map.
So, bottom line, you probably want to make the offscreen map the same size as the window client area, and be sure to move the BitBlt() call down so that it happens after all the drawing ops on the off-screen map.