MFC displaying png with transparent background
Asked Answered
D

3

6

I need a way to display a PNG image with transparent background on a background with gradient color.

I have tried this:

CImage img;
CBitmap bmp;
img.Load(_T(".\\res\\foo.png"));
bmp.Attach(img.Detach());
CDC dcStatus;
dcStatus.CreateCompatibleDC(&dc);
dcStatus.SelectObject(&bmp);
dcStatus.SetBkColor(TRANSPARENT);
dc.BitBlt(rectText.left + 250, rectText.top, 14, 14, &dcStatus, 0, 0, SRCCOPY);
bmp.DeleteObject();

but foo.png gets black background wherever it is transparent in original image.

I did try to do a new bitmap that was painted with transparent color and did all possible operations on it, but that didn't help. Sample of one permutation:

CImage img;
CBitmap bmp;
img.Load(_T(".\\res\\foo.png"));
bmp.Attach(img.Detach());
CBitmap bmpMaska;
bmpMaska.CreateBitmap(14, 14, 1, 1, NULL);
CDC dcStatus;
dcStatus.CreateCompatibleDC(&dc);
dcStatus.SelectObject(&bmp);
CDC dcMaska;
dcMaska.CreateCompatibleDC(&dc);
dcMaska.SelectObject(&bmpMaska);
dcMaska.SetBkColor(dcStatus.GetPixel(0, 0));
//TODO: Bitmap ni transparent
dc.BitBlt(rectText.left + 250, rectText.top, 14, 14, &dcMaska, 0, 0, SRCCOPY);
dc.BitBlt(rectText.left + 250, rectText.top, 14, 14, &dcStatus, 0, 0, SRCAND);  
bmp.DeleteObject();
bmpMaska.DeleteObject();

This did not do the trick. Either, there was all black square on the screen, or the result was the same as original.

I have also checked AlphaBlend API, but my code must be pure MFC + C++ witthout any additional APIs. [Edit]: Company policy is as little APIs as possible. The code is supposed to run on embedded windows systems in real time.

[Edit 2]: I am not bound to PNG image format, meaning, anything that will display as transparent, goes.

Please, tell me what am I doing wrong?

Dinar answered 22/10, 2014 at 21:38 Comment(6)
The old bitmap calls that MFC exposes can't handle alpha. AlphaBlend is the way to go, it's part of the same Windows API that's used internally by MFC. What's the reason for not using it?Aestival
Stupid company policy of using as little APIs as possible. Specially, since application runs on embedded windows systems.Dinar
Good luck to you then.Aestival
Oi @MarkRansom... Assuming I use AlphaBlend... what would I need to do to achieve desired result. As far as I can see from samples AlphaBlend is used to set opacity.Dinar
I forgot to ask, is the .png file 8 or 24 bits?Aestival
I didn't do any reduction, so basically it is either 24 or 32 bit.Dinar
C
7
  • Convert the png to a 32 bit bmp file. Google 'AlphaConv' and use that. PNG's are a PITA to work with - several API's claim to support them but actually don't, or only partially, or only on some platforms etc.
  • Load your 32-bit bmp using CBitmap::LoadBitmap (from resources - this is by far the easiest)
  • Then use CDC::AlphaBlend() to draw your bitmap. TransparentBlt() doesn't work with an alpha channel - it's just either draw the pixel or not. AlphaBlend is part of the win32 API, and you could still investigate CImage::AlphaBlend if you'd like, but that has a bug somewhere (I forgot what exactly) so I always use the raw ::AlphaBlend.
  • But beware - you need to premultipy the alpha channel for correct display. See my answer on How to draw 32-bit alpha channel bitmaps? .

No variation of what you described will work. You need another API to get this to work. Or, you could use GetDIBits and do your own version of ::AlphaBlend() :)

Colwen answered 23/10, 2014 at 23:42 Comment(0)
M
3

You can create your own custom control based on GDI+ which will use the WM_PAINT event to draw the given image with transparent background. That allows using and supporting PNG resources (and files) directly. I explained what I did in this article. enter image description here

See also: https://github.com/securedglobe/SG_PNG

There are two parts:

  1. Setting the image

    Status SetPNGImage(UINT nIDResource) 
    {
       // Get the instance handle of the application HINSTANCE
       hInstance = AfxGetInstanceHandle();
    
       // Find the specified resource in the application's executable file
       HRSRC hResource = ::FindResource(hInstance, MAKEINTRESOURCE(nIDResource), _T("PNG"));
       if (!hResource)
       {
           return GenericError; // Resource not found
       }
       // Get the size of the resource
       DWORD imageSize = ::SizeofResource(hInstance, hResource);
    
      // Get a pointer to the resource data
      const void* pResourceData = ::LockResource(::LoadResource(hInstance, hResource));
      if (!pResourceData)
      {
           return OutOfMemory; // Failed to lock resource
      }
    
      // Allocate global memory to hold the resource data
      HGLOBAL hBuffer = ::GlobalAlloc(GMEM_MOVEABLE, imageSize);
      if (!hBuffer)
      {
         return OutOfMemory; // Memory allocation failed
      }
    
      // Lock the allocated memory and copy the resource data into it
      void* pBuffer = ::GlobalLock(hBuffer);
      if (!pBuffer)
      {
         ::GlobalFree(hBuffer); // Failed to lock memory, free the buffer
         return OutOfMemory;
      }
      CopyMemory(pBuffer, pResourceData, imageSize);
    
      // Create an IStream object from the allocated memory
      IStream* pStream = NULL;
      if (::CreateStreamOnHGlobal(hBuffer, FALSE, &pStream) != S_OK)
      {
         ::GlobalUnlock(hBuffer);
         ::GlobalFree(hBuffer);
         return GenericError; // Failed to create stream
      }
    
      // Delete the previous bitmap if it exists
      delete m_pBitmap;
    
      // Create a GDI+ Bitmap object from the stream
      m_pBitmap = Bitmap::FromStream(pStream);
    
      // Release the IStream object
      pStream->Release();
    
      // Unlock and free the allocated memory
      ::GlobalUnlock(hBuffer);
      ::GlobalFree(hBuffer);
    
      // Check if the bitmap was created successfully
      if (m_pBitmap == NULL)
      {
         return OutOfMemory; // Failed to create bitmap
      }
    
      Status status = m_pBitmap->GetLastStatus();
      if (status != Ok)
      {
          delete m_pBitmap;
         m_pBitmap = NULL;
      }
    
      return status;
    

    }

Then, during OnPaint(), the image is drawn with full transparency.

void OnPaint()
{
    CPaintDC dc(this); // device context for painting
    CRect rect;
    GetClientRect(&rect);

    if (m_pBitmap != nullptr)
    {
        Graphics graphics(dc.GetSafeHdc());

        // Get the dimensions of the image
        int imageWidth = m_pBitmap->GetWidth();
        int imageHeight = m_pBitmap->GetHeight();

        // Calculate the scaling factors to fit the image inside the control
        float scaleX = static_cast<float>(rect.Width()) / imageWidth;
        float scaleY = static_cast<float>(rect.Height()) / imageHeight;
        float scale = min(scaleX, scaleY); // Use the minimum scaling factor to preserve aspect ratio

        // Calculate the dimensions of the scaled image
        int scaledWidth = static_cast<int>(imageWidth * scale);
        int scaledHeight = static_cast<int>(imageHeight * scale);

        // Calculate the position to center the scaled image within the control
        int xPos = (rect.Width() - scaledWidth) / 2;
        int yPos = (rect.Height() - scaledHeight) / 2;

        // Draw the scaled image
        graphics.DrawImage(m_pBitmap, xPos, yPos, scaledWidth, scaledHeight);
    }
}

The control obtains a device context (CPaintDC) for painting and retrieves the client area's dimensions using GetClientRect() by utilizing the GDI+ library to draw the image onto the device context. The drawing process involves calculating the appropriate scaling factors to ensure the image fits proportionally within the control's boundaries while preserving its aspect ratio.

The scaled image is then drawn onto the device context using the Graphics::DrawImage() method, ensuring that it is centered within the control.

Motive answered 2/6, 2024 at 18:45 Comment(1)
Use of GDI+ doesn't magically make a window transparent. The window has to be specifically tagged so that the system knows to render what would otherwise be occluded by the windows' area. As written, this answer is not useful.Platt
S
2

Based on what you're asking for (simply some set of pixels being transparent) it's probably easiest to use a 24- or 32-bit BMP, and simply pick a color to treat as transparent. Pre-process your picture to take any pixel that precisely matches your transparent color and increase change the least significant bit of one of the channels.

Given 8 bits per channel, this won't normally cause a visible change. Then draw the pixels you want transparent in one exact color. This is easy to do in something like a paint program.

You can then draw that to your DC using TransparentBlt. That lets you specify the color you want to treat as transparent.

Shroudlaid answered 22/10, 2014 at 22:15 Comment(1)
Would that work with anti-aliasing as well? Basically, my image is a painted circle on transparent background. If I change transparent to let's say pink, some pixels will not be pink, but will not have the color of either app background or circle itself.Dinar

© 2022 - 2025 — McMap. All rights reserved.