Creating an icon with transparency (alpha channel) from a bitmap

82 Views Asked by At

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;
1

There are 1 best solutions below

4
Remy Lebeau On

To make a transparent icon, you need to use the hbmMask bitmap to "mask out" the pixels of the hbmColor bitmap which you want to be transparent. But you are not doing that. You are creating an empty 1x1 bitmap for the hbmMask so nothing in hmbColor is getting masked out.

For a color icon, the hbmColor and hbmMask bitmaps must be the same dimensions, per the ICONINFO documentation:

For color icons, the hbmMask and hbmColor bitmaps are the same size, each of which is the size of the icon.

And per Raymond Chen's blog:

The evolution of the ICO file format, part 2: Now in color!

For pixels you want to be transparent, set your mask to white and your image to black. For pixels you want to come from your icon, set your mask to black and your image to the desired color.

So, in your case:

  • create your hbmColor bitmap and fill it with a unique background color (ie, Fuchsia is a common choice), then draw on it as needed.

  • Then, create the hbmMask bitmap with the same dimensions as hbmColor. For each pixel in hbmColor, if it has the background color then set the corresponding hbmMask pixel 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:

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.