简体   繁体   中英

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);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM