I'm looking for a way to create an icon for the system tray application that displays information using a number that appears on it.
Creating a DC and drawing the number on it is not a problem (even with an alpha channel), but I could not in any way transfer it to the icon correctly.
Here's something I tried recently, which seems close to what I need, but when I try to draw it in a window (using DrawIconEx) it shows a black square around the digits, not the window background.
Can someone direct me to correct this code so that the transparency is transferred correctly?
Thank you!
function CreateHiColorIcon(DC: HDC; nWidth, nHeight: Integer; Status: Byte; Color: COLORREF): HICON;
var
lpbi: PBITMAPINFO;
lpBits: Pointer;
hMaskBmp, hBmp, hOldBitmap: HBITMAP;
hMemDC: HDC;
OldFnt, Fnt: HFONT;
Sts: string;
R: TRect;
IconInfo: TIconInfo;
Icon: HICON;
begin
Result := 0;
GetMem(lpbi, SizeOf(TBITMAPINFOHEADER) + SizeOf(TRGBQuad) * 256);
try
with lpbi^.bmiHeader do
begin
biSize := SizeOf(TBITMAPINFOHEADER);
biWidth := nWidth;
biHeight := nHeight;
biPlanes := 1;
biBitCount := 32; // Use a 32-bit DIB section with an alpha channel.
biCompression := BI_RGB;
biSizeImage := 0;
biXPelsPerMeter := 0;
biYPelsPerMeter := 0;
biClrUsed := 0;
biClrImportant := 0;
end;
hBmp := CreateDIBSection(DC, lpbi^, DIB_RGB_COLORS, lpBits, 0, 0);
if hBmp = 0 then
RaiseLastOSError;
try
hMemDC := CreateCompatibleDC(DC);
if hMemDC = 0 then
RaiseLastOSError;
try
hOldBitmap := SelectObject(hMemDC, hBmp);
if hOldBitmap = 0 then
RaiseLastOSError;
try
Sts := IntToStr(Status);
Fnt := GetNCMFont; // Uses SystemParametersInfo to get the font used for the window titles (which I use for drawing)
OldFnt := SelectObject(hMemDC, Fnt);
R := TRect.Create(0, 0, nWidth, nHeight);
SetBkMode(hMemDC, TRANSPARENT);
SetTextColor(hMemDC, Color);
DrawTextEx(hMemDC, PWideChar(Sts), -1, R,
DT_CENTER or DT_VCENTER or DT_SINGLELINE or DT_NOCLIP, nil);
hMaskBmp := CreateBitmap(nWidth, nHeight, 1, 1, nil);
IconInfo.fIcon := TRUE;
IconInfo.xHotspot := 0;
IconInfo.yHotspot := 0;
IconInfo.hbmMask := hMaskBmp;
IconInfo.hbmColor := hBmp;
Icon := CreateIconIndirect(IconInfo);
if Icon = 0 then
RaiseLastOSError;
Result := Icon;
finally
SelectObject(hMemDC, hOldBitmap);
SelectObject(hMemDC, OldFnt);
DeleteObject(Fnt);
end;
finally
DeleteDC(hMemDC);
end;
finally
DeleteObject(hBmp);
DeleteObject(hMaskBmp);
end;
finally
FreeMem(lpbi);
end;
end;
To make a transparent icon, you need to use the
hbmMaskbitmap to "mask out" the pixels of thehbmColorbitmap which you want to be transparent. But you are not doing that. You are creating an empty 1x1 bitmap for thehbmMaskso nothing inhmbColoris getting masked out.For a color icon, the
hbmColorandhbmMaskbitmaps must be the same dimensions, per theICONINFOdocumentation:And per Raymond Chen's blog:
The evolution of the ICO file format, part 2: Now in color!
So, in your case:
create your
hbmColorbitmap and fill it with a unique background color (ie, Fuchsia is a common choice), then draw on it as needed.Then, create the
hbmMaskbitmap with the same dimensions ashbmColor. For each pixel inhbmColor, if it has the background color then set the correspondinghbmMaskpixel to white, otherwise set it to black.Then create the icon with the 2 bitmaps.
To create a "semi-transparent" (ie alpha-blended) icon is mostly the same approach. The difference is that:
the
hbmColorbitmap must be a 32bit DIB with an alpha channel (useBITMAPV5HEADERwithCreateDIBSection())the
hbmMaskbitmap must be empty.See MS KB Q318876: How To Create an Alpha Blended Cursor or Icon in Windows XP.
I just realized that you are creating a 32bit color bitmap. But I don't see you filling its background or alpha channel with any pixel data before drawing the text onto it.