How to draw text using CDC with transparent background on CBitmap? - c++

I have the following code which sort of works provided you mask out the pink pixels however what I actually want is transparent bits like a PNG file so that I can avoid alpha blending issues and the need mask out a specific color everywhere the bitmap will be used.
CClientDC dc(pWnd);
CDC memDC;
if(!memDC.CreateCompatibleDC(&dc))
return NULL;
CRect bitmapRect;
bitmapRect.SetRectEmpty();
CFont* pOldFont = memDC.SelectObject(pWnd->GetFont());
CSize fontSize = memDC.GetTextExtent(imageText);
bitmapRect.right = fontSize.cx;
bitmapRect.bottom = fontSize.cy;
CBitmap bitmap;
if(!bitmap.CreateCompatibleBitmap(&dc, bitmapRect.Width(), bitmapRect.Height()))
return NULL;
CBitmap* pOldMemDCBitmap = memDC.SelectObject(&bitmap);
memDC.FillSolidRect(&bitmapRect, RGB(255,0,255));
//memDC.SetBkMode(TRANSPARENT); // doesn't work
//memDC.SetBkColor(TRANSPARENT); // doesn't work
memDC.SetTextColor(GetSysColor(COLOR_WINDOWTEXT));
//memDC.DrawText(imageText, bitmapRect, DT_TOP|DT_LEFT|DT_NOCLIP); // same difference
memDC.TextOut(0, 0, imageText);
memDC.SelectObject(pOldMemDCBitmap);
memDC.SelectObject(pOldFont);
memDC.DeleteDC();
CImageList bmImage;
bmImage.Create(bitmapRect.Width(), bitmapRect.Height(), ILC_COLOR32|ILC_MASK, 0, 1);
// this masks out the pink but for some windows blends edges of text causing pink text instead of black!
bmImage.Add(&bitmap, RGB(255,0,255));
Is just the bug filled beast that is MFC misbehaving or am I missing something?

Simple DrawText() with transparent background without MFC:
// in my case a user drawn button:
_windowHandle = CreateWindowEx(...);
SendMessage(_windowHandle, WM_SETFONT, (WPARAM)font, (LPARAM)NULL);
...
// WM_DRAWITEM
SetTextColor(hDC, RGB(216, 27, 27));
SetBkMode(hDC, TRANSPARENT);
RECT rect = { 0, 0, backgroundBitmap.bmWidth, backgroundBitmap.bmHeight };
DrawText(hDC, _text.c_str(), -1, &rect, DT_CENTER | DT_WORDBREAK);
--hfrmobile

About 10 minutes after asking this I read my own comment and realized that "some windows" means it was related to the windows being passed in. Specifically the font being used from said window. Fonts with default properties were exhibiting the strange blending.
At the end of the day I determined I needed to modify the font to turn off the things messing up my drawing code. I eventually narrowed it down to the one culprit causing the problem:
CClientDC dc(pWnd);
CDC memDC;
if(!memDC.CreateCompatibleDC(&dc))
return NULL;
LOGFONT tempFont;
CFont* winFont = pWnd->GetFont();
if (winFont)
winFont->GetLogFont(&tempFont);
else
{
// generate a likely font
SecureZeroMemory(&tempFont, sizeof(LOGFONT));
//TEXTMETRIC txt;
//GetTextMetrics(memDC, &txt);
//tempFont.lfHeight = txt.tmHeight * -1; // 16 is too big looking
tempFont.lfHeight = -12;
tempFont.lfWeight = FW_NORMAL;
tempFont.lfCharSet = DEFAULT_CHARSET;
wcscpy_s(tempFont.lfFaceName, L"Segoe UI"); // Win7 control default
}
tempFont.lfQuality = NONANTIALIASED_QUALITY; // this is the fix!!!
CFont newFont;
newFont.CreateFontIndirect(&tempFont);
CFont* pOldFont = memDC.SelectObject(&newFont);
// ... other stuff same as before ...
So I still FillSolidRect pink then draw my icons, text, whatever I want, etc. Then mask out the pink pixels. With the font quality adjustment it no longer blends pink into the font text and it looks good. The else case above creates a default font to use in case the CWnd* passed in doesn't have a valid one specified.

Related

How to maintain transparency when writing text on a Windows System Tray icon from unmanaged C program

I have a MFC C++ (unmanaged) Windows application that uses a “standard” icon in the System Tray. This icon was created & edited using Visual Studio and is 32x32 pixels with only 4bit colour (according to VS's Resource Editor).
With Visual Studio, I also set a transparent background (shown as white in the “before” image).
I wish to dynamically change the icon by writing 2 digits (1-99) on top of it.
Using the code below (based on that in this question: How to draw text with transparency using GDI?) to superimpose “55” in yellow on the icon, it works except that the transparency disappears (it appears black in the “after” image and on the System Tray). My actual code differs very very slightly in that the font size (20), font name (Courier New), text colour (yellow - RGB(255, 255, 0)) and the numeric value (55) are run-time variables rather than fixed values.
Any suggestions on how to make the background remain transparent as far as the System Tray is concerned gratefully received.
These images have been captured using MS’s Snipping tool with the image open in MS Paint as a 32x32 icon wouldn't be very visible as-is.
Before Image:
After image:
Code:
void CreateNewIcon(HICON &hNewIcon, HICON hBackgroundIcon)
{
::DestroyIcon(hNewIcon);
// First create font
LOGFONT lf = { 0 };
lf.lfHeight = -20;
lf.lfWeight = FW_BOLD;
lf.lfOutPrecision = OUT_TT_PRECIS;
lf.lfQuality = CLEARTYPE_QUALITY;
wmemset(lf.lfFaceName, 0, LF_FACESIZE);
lstrcpy(lf.lfFaceName, L"Courier New");
HFONT hFont = ::CreateFontIndirect(&lf);
ICONINFO ii = { 0 };
::GetIconInfo(hBackgroundIcon, &ii);
BITMAP bm = { 0 };
::GetObject(ii.hbmColor, sizeof(bm), &bm);
SIZE szBmp = { bm.bmWidth, bm.bmHeight };
HDC hDc = ::GetDC(NULL);
HDC hMemDC = ::CreateCompatibleDC(hDc);
HGDIOBJ hOldBmp = ::SelectObject(hMemDC, ii.hbmColor);
HGDIOBJ hOldFont = ::SelectObject(hMemDC, hFont);
::SetBkMode(hMemDC, TRANSPARENT);
::SetTextColor(hMemDC, RGB(255, 255, 0));
::TextOut(hMemDC, 0, 8, L"55", 2);
::SelectObject(hMemDC, hOldFont);
::SelectObject(hMemDC, hOldBmp);
// We need a simple mask bitmap for the icon
HBITMAP hBmpMsk = ::CreateBitmap(szBmp.cx, szBmp.cy, 1, 1, NULL);
ICONINFO ii2 = { 0 };
ii2.fIcon = TRUE;
ii2.hbmColor = ii.hbmColor;
ii2.hbmMask = hBmpMsk;
// Create updated icon
hNewIcon = ::CreateIconIndirect(&ii2);
// Cleanup
::DeleteObject(hBmpMsk);
::DeleteDC(hMemDC);
::ReleaseDC(NULL, hDc);
::DeleteObject(ii.hbmColor);
::DeleteObject(ii.hbmMask);
::DeleteObject(hFont);
}
There are multiple issues with our code:
You are trying to draw cleartype-quality text over transparent icon part. But cleartype font rendering must be performed over opaque background because it needs to inspect background color. So you should switch to Anitialiased quality (or to not antialiased quality) or provide opaque background for your text.
When you create hBmpMsk you skip it's content initialization by supplying NULL for bits pointer, so resulting icon will actually have completely random transparency. You need to fill this mask bitmat appropriately.
Also you probably need to switch to higher bit depth because monochrome bitmap mask can't handle semitransparent parts of antialiased text.
update
I think you should draw cleartype text but with opaque background, then get text rectangle using something like GetTextExtentPoint32, then copy data from the original bitmap mask into hBmpMsk and then finally fill white (text) rectangle on it so the new icon will preserve transparency from original and has opaque text block.
Thanks for all the help from VTT without which I wouldn't have been able to get this far. This appears to work for me.
void CreateNewIcon(HICON &hNewIcon, HICON hBackgroundIcon)
{
::DestroyIcon(hNewIcon);
HDC hDc = ::GetDC(NULL);
HDC hMemDC = ::CreateCompatibleDC(hDc);
// Load up background icon
ICONINFO ii = { 0 };
::GetIconInfo(hBackgroundIcon, &ii);
HGDIOBJ hOldBmp = ::SelectObject(hMemDC, ii.hbmColor);
// Create font
LOGFONT lf = { 0 };
lf.lfHeight = -20;
lf.lfWeight = FW_BOLD;
lf.lfOutPrecision = OUT_TT_PRECIS;
lf.lfQuality = ANTIALIASED_QUALITY;
wmemset(lf.lfFaceName, 0, LF_FACESIZE);
lstrcpy(lf.lfFaceName, L"Courier New");
HFONT hFont = ::CreateFontIndirect(&lf);
HGDIOBJ hOldFont = ::SelectObject(hMemDC, hFont);
// Write text
::SetBkMode(hMemDC, TRANSPARENT);
::SetTextColor(hMemDC, RGB(255, 255, 0));
::TextOut(hMemDC, 0, 8, L"55", 2);
// Set up mask
HDC hMaskDC = ::CreateCompatibleDC(hDc);
HGDIOBJ hOldMaskBmp = ::SelectObject(hMaskDC, ii.hbmMask);
// Also write text on here
HGDIOBJ hOldMaskFont = ::SelectObject(hMaskDC, hFont);
::SetBkMode(hMaskDC, TRANSPARENT);
::SetTextColor(hMaskDC, RGB(255, 255, 0));
::TextOut(hMaskDC, 0, 8, L"55", 2);
// Get handle to create mask bitmap
HBITMAP hMaskBmp = (HBITMAP)::SelectObject(hMaskDC, hOldMaskBmp);
// Use new icon bitmap with text and new mask bitmap with text
ICONINFO ii2 = { 0 };
ii2.fIcon = TRUE;
ii2.hbmMask = hMaskBmp;
ii2.hbmColor = ii.hbmColor;
// Create updated icon
hNewIcon = ::CreateIconIndirect(&ii2);
// Cleanup bitmap mask
::DeleteObject(hMaskBmp);
::DeleteDC(hMaskDC);
// Cleanup font
::SelectObject(hMaskDC, hOldMaskFont);
::SelectObject(hMemDC, hOldFont);
::DeleteObject(hFont);
// Release background bitmap
::SelectObject(hMemDC, hOldBmp);
// Delete background icon bitmap info
::DeleteObject(ii.hbmColor);
::DeleteObject(ii.hbmMask);
::DeleteDC(hMemDC);
::ReleaseDC(NULL, hDc);
}

How to draw text with transparency using GDI?

My goal is to dynamically put some arbitrary text into an HICON image (at runtime.) I'm using the following code:
//Error checks are omitted for brevity
//First create font
LOGFONT lf = {0};
lf.lfHeight = -58;
lf.lfWeight = FW_NORMAL;
lf.lfOutPrecision = OUT_TT_PRECIS; //Use TrueType fonts for anti-alliasing
lf.lfQuality = CLEARTYPE_QUALITY;
lstrcpy(lf.lfFaceName, L"Segoe UI");
HFONT hFont = ::CreateFontIndirect(&lf);
//HICON hIcon = original icon to use as a source
//I'm using a large 256x256 pixel icon
hIcon = (HICON)::LoadImage(theApp.m_hInstance, MAKEINTRESOURCE(IDI_ICON_GREEN_DIAMOND), IMAGE_ICON, 256, 256, LR_DEFAULTCOLOR);
ICONINFO ii = {0};
::GetIconInfo(hIcon, &ii);
BITMAP bm = {0};
::GetObject(ii.hbmColor, sizeof(bm), &bm);
SIZE szBmp = {bm.bmWidth, bm.bmHeight};
HDC hDc = ::GetDC(hWnd);
HDC hMemDC = ::CreateCompatibleDC(hDc);
HGDIOBJ hOldBmp = ::SelectObject(hMemDC, ii.hbmColor);
HGDIOBJ hOldFont = ::SelectObject(hMemDC, hFont);
::SetBkMode(hMemDC, TRANSPARENT);
::SetTextColor(hMemDC, RGB(255, 0, 0)); //Red text
//Draw text
//NOTE that DrawText API behaves in a similar way
::TextOut(hMemDC, 0, 0, L"Hello", 5);
::SelectObject(hMemDC, hOldFont);
::SelectObject(hMemDC, hOldBmp);
//We need a simple mask bitmap for the icon
HBITMAP hBmpMsk = ::CreateBitmap(szBmp.cx, szBmp.cy, 1, 1, NULL);
ICONINFO ii2 = {0};
ii2.fIcon = TRUE;
ii2.hbmColor = ii.hbmColor;
ii2.hbmMask = hBmpMsk;
//Create updated icon
HICON hIcon2 = ::CreateIconIndirect(&ii2);
//Cleanup
::DeleteObject(hBmpMsk);
::DeleteDC(hMemDC);
::ReleaseDC(hWnd, hDc);
::DeleteObject(ii.hbmColor);
::DeleteObject(ii.hbmMask);
::DeleteObject(hFont);
and then I can display the icon in my window from OnPaint() handler (so that I can see how it turns out) as such:
::DrawIconEx(dc.GetSafeHdc(), 0, 0,
hIcon2,
256, 256, NULL,
::GetSysColorBrush(COLOR_BTNFACE),
DI_NORMAL);
So here's what I get:
To see what's going on pixel-wise in my hIcon2 I called GetDIBits on its ii.hbmColor from the code above. The resulting pixel array where my word "Hello" was supposed to be shown looked like this:
The pixels are encoded as BGRA in that memory dump, so the 4th byte in each DWORD stands for transparency: 0=transparent, FF=opaque. But in this case TextOut doesn't fill out transparency, or leaves it as 0, which is interpreted as "fully transparent." Instead it seems to pre-multiply it into the RGB colors themselves.
Note that if I keep looking further down the same bitmap, where the green diamond begins, the image pixels seem to have transparency bytes set correctly:
Any idea how to draw text so that the API could set those transparency bytes?
EDIT: As was suggested below I tried the following GDI+ method:
HGDIOBJ hOldBmp = ::SelectObject(hMemDC, ii.hbmColor);
Graphics grpx(hMemDC);
RectF rcfTxt(0.0f, 0.0f, (REAL)szBmp.cx, (REAL)szBmp.cy);
Font gdiFont(L"Segoe UI", 58.0f, FontStyleRegular, UnitPixel);
SolidBrush gdiBrush(Color(255, 0, 0));
StringFormat gdiSF;
gdiSF.SetAlignment(StringAlignmentNear);
gdiSF.SetFormatFlags(StringFormatFlagsNoWrap);
gdiSF.SetHotkeyPrefix(HotkeyPrefixNone);
//The reason I was using GDI was because I was setting
//spacing between letters using SetTextCharacterExtra()
//Unfortunately with GDI+ this does not work!
HDC hTmpDC = grpx.GetHDC();
::SetTextCharacterExtra(hTmpDC, -4); //This doesn't do anything!
grpx.ReleaseHDC(hTmpDC);
grpx.DrawString(L"Hello", 5, &gdiFont, rcfTxt, &gdiSF, &gdiBrush);
::SelectObject(hMemDC, hOldBmp);
and besides not being able to set character spacing (which I could with GDI using SetTextCharacterExtra) here's what I got (slightly enlarged for visibility):
So clearly still an issue with transparency.
Taken from an old post by Microsoft MVP Mike D Sutton here.
When you create a DC it initially has default 'stock' objects selected
into it, including the stock 1*1*1 Bitmap. Since there is a Bitmap
already selected into the DC when you call DrawText() it will still
try and render to it even though pretty much everything (apart from
one pixel) will be clipped.
What you need to do is to create a Bitmap,
either DDB or DIBSection, and select that into your DC before drawing
to it.
First though you need to find the size of your Bitmap since you
want it large enough to display your text in, so for that you use the
DrawText() call again on the initial DC but include the DT_CALCRECT
flag. What this does is rather than drawing anything it simply
measures how large the text is and dumps that into the RECT you pass
the call. From here you can go ahead and create your DIBSection using
those dimensions and select it into your DC. Finally perform your
existing DrawText ()call (you may also want to use SetBkMode/Color())
which will render the text to the DIBSection from which you can get at
the data.
This seems to work pretty well here:
HBITMAP CreateAlphaTextBitmap(LPCSTR inText, HFONT inFont, COLORREF inColour) {
int TextLength = (int)strlen(inText);
if (TextLength <= 0) return NULL;
// Create DC and select font into it
HDC hTextDC = CreateCompatibleDC(NULL);
HFONT hOldFont = (HFONT)SelectObject(hTextDC, inFont);
HBITMAP hMyDIB = NULL;
// Get text area
RECT TextArea = {0, 0, 0, 0};
DrawText(hTextDC, inText, TextLength, &TextArea, DT_CALCRECT);
if ((TextArea.right > TextArea.left) && (TextArea.bottom > TextArea.top)) {
BITMAPINFOHEADER BMIH;
memset(&BMIH, 0x0, sizeof(BITMAPINFOHEADER));
void *pvBits = NULL;
// Specify DIB setup
BMIH.biSize = sizeof(BMIH);
BMIH.biWidth = TextArea.right - TextArea.left;
BMIH.biHeight = TextArea.bottom - TextArea.top;
BMIH.biPlanes = 1;
BMIH.biBitCount = 32;
BMIH.biCompression = BI_RGB;
// Create and select DIB into DC
hMyDIB = CreateDIBSection(hTextDC, (LPBITMAPINFO)&BMIH, 0, (LPVOID*)&pvBits, NULL, 0);
HBITMAP hOldBMP = (HBITMAP)SelectObject(hTextDC, hMyDIB);
if (hOldBMP != NULL) {
// Set up DC properties
SetTextColor(hTextDC, 0x00FFFFFF);
SetBkColor(hTextDC, 0x00000000);
SetBkMode(hTextDC, OPAQUE);
// Draw text to buffer
DrawText(hTextDC, inText, TextLength, &TextArea, DT_NOCLIP);
BYTE* DataPtr = (BYTE*)pvBits;
BYTE FillR = GetRValue(inColour);
BYTE FillG = GetGValue(inColour);
BYTE FillB = GetBValue(inColour);
BYTE ThisA;
for (int LoopY = 0; LoopY < BMIH.biHeight; LoopY++) {
for (int LoopX = 0; LoopX < BMIH.biWidth; LoopX++) {
ThisA = *DataPtr; // Move alpha and pre-multiply with RGB
*DataPtr++ = (FillB * ThisA) >> 8;
*DataPtr++ = (FillG * ThisA) >> 8;
*DataPtr++ = (FillR * ThisA) >> 8;
*DataPtr++ = ThisA; // Set Alpha
}
}
// De-select bitmap
SelectObject(hTextDC, hOldBMP);
}
}
// De-select font and destroy temp DC
SelectObject(hTextDC, hOldFont);
DeleteDC(hTextDC);
// Return DIBSection
return hMyDIB;
}
If you need an example of how to call it then try something like this
(inDC is the DC to render to):
void TestAlphaText(HDC inDC, int inX, int inY) {
const char *DemoText = "Hello World!\0";
RECT TextArea = {0, 0, 0, 0};
HFONT TempFont = CreateFont(50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "Arial\0");
HBITMAP MyBMP = CreateAlphaTextBitmap(DemoText, TempFont, 0xFF);
DeleteObject(TempFont);
if (MyBMP) { // Create temporary DC and select new Bitmap into it
HDC hTempDC = CreateCompatibleDC(inDC);
HBITMAP hOldBMP = (HBITMAP)SelectObject(hTempDC, MyBMP);
if (hOldBMP) {
BITMAP BMInf; // Get Bitmap image size
GetObject(MyBMP, sizeof(BITMAP), &BMInf);
// Fill blend function and blend new text to window
BLENDFUNCTION bf;
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = 0x80;
bf.AlphaFormat = AC_SRC_ALPHA;
AlphaBlend(inDC, inX, inY, BMInf.bmWidth, BMInf.bmHeight,
hTempDC, 0, 0, BMInf.bmWidth, BMInf.bmHeight, bf);
// Clean up
SelectObject(hTempDC, hOldBMP);
DeleteObject(MyBMP);
DeleteDC(hTempDC);
}
}
}
All credit to answer and code go to original posters on that forum, I've simply reposted it so that this answer will be valid if the links die.
This reply is coming almost 3 years after the question was posted, but people still consult these things long into the future. So I'll explain what's happening.
DrawText (and other GDI text functions) will work on a transparent bitmap. The text is not coming out black even though it displays that way. The alpha channel is set to 0 on all pixels the text draws to, overriding whatever alpha you had set previously. If you set an alpha value in SetTextColor the text will render all black. If you're feeling ambitious you can run through pixel by pixel and target anything not your fill color (which requires a single fill color) but the problem then becomes one of the nature of ClearType being overridden and all alphas are set to whatever you set them to. The text ends up looking very funky. If you use a constant alpha for your background fill you can simply do a blanket run across the entire bitmap's bits after the text is drawn and reset all the alpha values. Since you have to read a byte to determine if it's background or not, you might as well just set every pixel's alpha to whatever the standard alpha is for that image and bypass the slow compares. This works reasonably well and I've found it to be very acceptable. In this day and age, MS should have taken care of this long ago but it's not to be.
https://learn.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-antialiasing-with-text-use
Gdiplus::Bitmap bmp( your_Width, your_Height, PixelFormat64bppARGB);
//PixelFormat64bppARGB ARGB needed
FontFamily fontFamily(L"Arial");
Font font(&fontFamily, 29, FontStyleRegular, UnitPoint);
Gdiplus::RectF rectF(00.0f, 10.0f, your_Width, your_Height);
StringFormat stringFormat;
SolidBrush solidBrush(Color(63, 0, 0, 255));
stringFormat.SetAlignment(StringAlignmentCenter);
//solidBrush Color(63, 0, 0, 255) ARGB neede
graphics.SetTextRenderingHint(TextRenderingHintAntiAlias);
graphics.DrawString("your_text", -1, &font, rectF, &stringFormat, &solidBrush);
//TextRenderingHintAntiAlias this needed

How to get a 32bpp bitmap/image from a GDI Device Context?

I am using code from this Project http://www.codeproject.com/Articles/9064/Yet-Another-Transparent-Static-Control in order to draw transparent button images from my subclassed Button control onto my CDialogEx.
This code is meant for legacy 24bpp GDI functions:
BOOL CTransparentStatic2::OnEraseBkgnd(CDC* pDC)
{
if (m_Bmp.GetSafeHandle() == NULL)
{
CRect Rect;
GetWindowRect(&Rect);
CWnd *pParent = GetParent();
ASSERT(pParent);
pParent->ScreenToClient(&Rect); //convert our corrdinates to our parents
//copy what's on the parents at this point
CDC *pDC = pParent->GetDC();
CDC MemDC;
MemDC.CreateCompatibleDC(pDC);
m_Bmp.CreateCompatibleBitmap(pDC,Rect.Width(),Rect.Height());
CBitmap *pOldBmp = MemDC.SelectObject(&m_Bmp);
MemDC.BitBlt(0,0,Rect.Width(),Rect.Height(),pDC,Rect.left,Rect.top,SRCCOPY);
MemDC.SelectObject(pOldBmp);
pParent->ReleaseDC(pDC);
}
else //copy what we copied off the parent the first time back onto the parent
{
CRect Rect;
GetClientRect(Rect);
CDC MemDC;
MemDC.CreateCompatibleDC(pDC);
CBitmap *pOldBmp = MemDC.SelectObject(&m_Bmp);
pDC->BitBlt(0,0,Rect.Width(),Rect.Height(),&MemDC,0,0,SRCCOPY);
MemDC.SelectObject(pOldBmp);
}
return TRUE;
}
However the background of my CDialogEx is being drawn with GDI+ 32bpp rendering like this:
BOOL CParentDialogEx::OnEraseBkgnd(CDC* pDC)
{
// Get GDI+ Graphics for the current Device Context
Graphics gr(*pDC);
// Get the client area
CRect clientRect;
GetClientRect(&clientRect);
// Draw the dialog background
// PLEASE NOTE: m_imgDlgBkgnd is a Gdiplus::Image with PNG format ==> 32bpp Image
gr.DrawImage(m_imgDlgBkgnd, 0, 0, clientRect.Width(), clientRect.Height());
}
This causes the first code snippet to make a backup of a black rectangle instead of the 32bpp drawn content to it. This again causes my button control to always have a black background.
To make my problem clear, please see the pictures below:
Button images are being drawn onto the CDialogEx background (normally):
Button images are being drawn with the first code snippet
As you can see GDI 24bpp cannot see the dialog background. It just assumes plain black background. Only GDI+ could see it. However I was not able to find a way to get a bitmap from a Gdiplus::Graphics object.
How can I get a 32bpp background backup in order to draw my transparent images correctly?
Using no backup images at all causes the alpha blending of GDI+ to blur the background more and more on every draw.
After 3 days of figuring around I found something by testing. This can actually be done way easier and with 32bpp rendering!
// Get the client area on the parent
CRect Rect;
GetWindowRect(&Rect);
CWnd *pParent = GetParent();
ASSERT(pParent);
pParent->ScreenToClient(&Rect);
// Get the Parent's DC
CDC *parentDC = pParent->GetDC();
// GDI Code (only 24bpp support)
//CDC MemDC;
//CBitmap bmp;
//MemDC.CreateCompatibleDC(parentDC);
//bmp.CreateCompatibleBitmap(parentDC,Rect.Width(),Rect.Height());
//CBitmap *pOldBmp = MemDC.SelectObject(&bmp);
//MemDC.BitBlt(0,0,Rect.Width(),Rect.Height(),parentDC,Rect.left,Rect.top,SRCCOPY);
//MemDC.SelectObject(pOldBmp);
// GDI+ Code with 32 bpp support (here comes the important change)
Bitmap* bmp = new Bitmap(Rect.Width(), Rect.Height());
Graphics bmpGraphics(bmp);
HDC hBmpDC = bmpGraphics.GetHDC();
BitBlt(hBmpDC, 0, 0, Rect.Width(), Rect.Height(), parentDC->GetSafeHdc(), Rect.left, Rect.top, SRCCOPY); // BitBlt is actually capable of doing 32bpp blts
// Release DCs
bmpGraphics.ReleaseDC(hBmpDC);
pParent->ReleaseDC(parentDC);
Here you go! 4 lines of code and you get a 32bpp Gdiplus::Bitmap! Which you can draw later on in OnEraseBkgnd:
// Get client Area
CRect rect;
GetClientRect(&rect);
// Draw copied background back onto the parent
Graphics gr(*pDC);
gr.DrawImage(bmp, 0, 0, rect.Width(), rect.Height());
Please note that the Bitmap should be a member variable for performance increase. It also has to be freed on the controls destruction.
Instead of Gdiplus::Image, use Gdiplus::Bitmap which has GetHBITMAP
In this example the background is set to red for png image with transparent background:
Gdiplus::Bitmap gdi_bitmap(L"c:\\test\\filename.png");
HBITMAP hbitmap = NULL;
gdi_bitmap.GetHBITMAP(Gdiplus::Color(128, 0, 0), &hbitmap);
CBitmap bmp;
bmp.Attach(hbitmap);
CDC memdc;
memdc.CreateCompatibleDC(pDC);
memdc.SelectObject(&bmp);
pDC->StretchBlt(0, 0, clientRect.Width(), clientRect.Height(), &memdc, 0, 0,
gdi_bitmap.GetWidth(), gdi_bitmap.GetHeight(), SRCCOPY);
Alternatively, you can use a brush to handle the painting:
CBrush m_Brush;
BOOL CMyDialog::OnInitDialog()
{
CDialogEx::OnInitDialog();
Gdiplus::Bitmap bitmap(L"c:\\test\\filename.png");
HBITMAP hbitmap = NULL;
bitmap.GetHBITMAP(Gdiplus::Color(128, 0, 0), &hbitmap);
CBitmap bmp;
bmp.Attach(hbitmap);
m_Brush.CreatePatternBrush(&bmp);
return 1;
}
HBRUSH CMyDialog::OnCtlColor(CDC* pDC, CWnd* wnd, UINT nCtlColor)
{
//uncomment these two lines if you only want to change dialog background
//if (wnd != this)
// return CDialogEx::OnCtlColor(pDC, wnd, nCtlColor);
if (nCtlColor == CTLCOLOR_STATIC)
{
pDC->SetTextColor(RGB(0, 128, 128));
pDC->SetBkMode(TRANSPARENT);
}
CPoint pt(0, 0);
if (wnd != this)
MapWindowPoints(wnd, &pt, 1);
pDC->SetBrushOrg(pt);
return m_Brush;
}
BOOL CMyDialog::OnEraseBkgnd(CDC* pDC)
{//don't do anything
return CDialogEx::OnEraseBkgnd(pDC);
}

Custom CTreeCtrl - How to modify text / selection color

First of all, i would customize - among other things - color of text and selection color(text background).
For example, text color should be blue; color of text background should be transparent.
So, I have overriden OnPaint() method;
I call SetTextColor() and SetBkColor() functions, but unfortunately I always get invalid colors, or I get an annoying "infinite loop flash" effect.
Here you can see his complete implementation.
void CustomTree::OnPaint()
{
CPaintDC dc(this);
CDC memDC;
memDC.CreateCompatibleDC(&dc);
CRect rcClip, rcClient;
dc.GetClipBox( &rcClip );
GetClientRect(&rcClient);
CBitmap bitmap;
bitmap.CreateCompatibleBitmap( &dc, rcClient.Width(), rcClient.Height() );
memDC.SelectObject( &bitmap );
CRgn rgn;
rgn.CreateRectRgnIndirect( &rcClip );
memDC.SelectClipRgn(&rgn);
rgn.DeleteObject();
/* WHAT IS the correct usage of SetText/Bk Color? */
// ::SetTextColor(memDC, RGB(0, 0, 255));
// ::SetBkColor(memDC, RGB(0, 0, 255));
// COLORREF col = SetTextColor(RGB(0,0,255));
// COLORREF co2 = memDC.SetTextColor(RGB(0,0,255));
// First let the control do its default drawing.
CWnd::DefWindowProc(WM_PAINT, (WPARAM)memDC.m_hDC, 0);
// do some others stuffs...
dc.BitBlt(rcClip.left, rcClip.top, rcClip.Width(), rcClip.Height(), &memDC,
rcClip.left, rcClip.top, SRCCOPY);
memDC.DeleteDC();
}
Where is the error?
Thanks
IT
If you want to change color and font for a treeview, you have to catch and respond to NM_CUSTOMDRAW. Simply setting the properties before calling the default is insufficient.

Creating a transparent window in C++ Win32

I'm creating what should be a very simple Win32 C++ app whose sole purpose it to ONLY display a semi-transparent PNG. The window shouldn't have any chrome, and all the opacity should be controlled in the PNG itself.
My problem is that the window doesn't repaint when the content under the window changes, so the transparent areas of the PNG are "stuck" with what was under the window when the application was initially started.
Here's the line where I setup the new window:
hWnd = CreateWindowEx(WS_EX_TOPMOST, szWindowClass, szTitle, WS_POPUP, 0, height/2 - 20, 40, 102, NULL, NULL, hInstance, 0);
For the call to RegisterClassEx, I have this set for the background:
wcex.hbrBackground = (HBRUSH)0;
Here is my handler for WM_PAINT message:
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
Gdiplus::Graphics graphics(hdc);
graphics.DrawImage(*m_pBitmap, 0, 0);
EndPaint(hWnd, &ps);
break;
}
One thing to note is that the application is always docked to the left of the screen and doesn't move. But, what's underneath the application may change as the user opens, closes or moves windows under it.
When the application first starts, it looks perfect. The transparent (and simi-transparent) parts of the PNG show through perfectly. BUT, when the background underneath the application changes, the background DOESN'T update, it just stays the same from when the application first started. In fact, WM_PAINT (or WM_ERASEBKGND does not get called when the background changes).
I've been playing with this for quite a while and have gotten close to getting 100% right, but not quite there. For instance, I've tried setting the background to (HBRUSH) NULL_BRUSH and I've tried handling WM_ERASEBKGND.
What can be done to get the window to repaint when the contents under it changes?
I was able to do exactly what I wanted by using the code from Part 1 and Part 2 of this series:
Displaying a Splash Screen with C++
Part 1: Creating a HBITMAP archive
Part 2: Displaying the window archive
Those blog posts are talking about displaying a splash screen in Win32 C++, but it was almost identical to what I needed to do. I believe the part that I was missing was that instead of just painting the PNG to the window using GDI+, I needed to use the UpdateLayeredWindow function with the proper BLENDFUNCTION parameter. I'll paste the SetSplashImage method below, which can be found in Part 2 in the link above:
void SetSplashImage(HWND hwndSplash, HBITMAP hbmpSplash)
{
// get the size of the bitmap
BITMAP bm;
GetObject(hbmpSplash, sizeof(bm), &bm);
SIZE sizeSplash = { bm.bmWidth, bm.bmHeight };
// get the primary monitor's info
POINT ptZero = { 0 };
HMONITOR hmonPrimary = MonitorFromPoint(ptZero, MONITOR_DEFAULTTOPRIMARY);
MONITORINFO monitorinfo = { 0 };
monitorinfo.cbSize = sizeof(monitorinfo);
GetMonitorInfo(hmonPrimary, &monitorinfo);
// center the splash screen in the middle of the primary work area
const RECT & rcWork = monitorinfo.rcWork;
POINT ptOrigin;
ptOrigin.x = 0;
ptOrigin.y = rcWork.top + (rcWork.bottom - rcWork.top - sizeSplash.cy) / 2;
// create a memory DC holding the splash bitmap
HDC hdcScreen = GetDC(NULL);
HDC hdcMem = CreateCompatibleDC(hdcScreen);
HBITMAP hbmpOld = (HBITMAP) SelectObject(hdcMem, hbmpSplash);
// use the source image's alpha channel for blending
BLENDFUNCTION blend = { 0 };
blend.BlendOp = AC_SRC_OVER;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
// paint the window (in the right location) with the alpha-blended bitmap
UpdateLayeredWindow(hwndSplash, hdcScreen, &ptOrigin, &sizeSplash,
hdcMem, &ptZero, RGB(0, 0, 0), &blend, ULW_ALPHA);
// delete temporary objects
SelectObject(hdcMem, hbmpOld);
DeleteDC(hdcMem);
ReleaseDC(NULL, hdcScreen);
}
Use the SetLayeredWindowAttributesarchive function, this allows you to set a mask color that will become transparent, thus allowing the background to show through.
You will also need to configure your window with the layered flag, e.g.:
SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED);
After that it's fairly simple:
// Make red pixels transparent:
SetLayeredWindowAttributes(hwnd, RGB(255,0,0), 0, LWA_COLORKEY);
When your PNG contains semi-transparent pixels that you want to blend with the background, this becomes more complicated. You could try looking at the approach in this CodeProject article:
Cool, Semi-transparent and Shaped Dialogs with Standard Controls for Windows 2000 and Above