3

I am in an MFC application. I created a bitmap using a memory DC I want to save it to DIB file.

I found this code to be most elegant so far:

void Save(CBitmap * bitmap) {
  CImage image;
  image.Attach((HBITMAP)pcBitmap->GetSafeHandle());
  image.Save("bla.bmp", Gdiplus::ImageFormatBMP);
}

The resulting file is 32 BPP colorspace with all alpha values set to '0'.

Now I want use the Bitmap as toolbar bitmap:

CMFCToolbar::GetImages()->Load("bla.bmp");

But all the icons are gone.

MFC internally calls PreMultiplyAlpha() when importing the bitmap. Then RGB components of all pixels are '0'. Effectively the whole bitmap was zeroed.

How can I set the alpha value for each pixel to '0xFF' before saving?

I tried:

void Save(CBitmap * bitmap) {
  CImage image;
  image.Attach((HBITMAP)pcBitmap->GetSafeHandle());
  image.SetHasAlphaChannel(true);
  image.AlphaBlend(myBitmapDC, 0, 0);
  image.Save("bla.bmp", Gdiplus::ImageFormatBMP);
}

But that affects only RGB values of the pixels.

So far I resisted to iterate over each pixel and modifying the memory of the bitmap. I'm asking for an elegant solution. Maybe a one-liner.

  • You want an alpha of 0xff, not 0 - don't you? – noelicus Jul 04 '18 at 15:11
  • 2
    Try to use GDI+ for saving, it handles alpha channels pretty well. `CImage` is just a wrapper for a `HBITMAP` which doesn't have any notion of alpha channel. – zett42 Jul 04 '18 at 16:54
  • CImage.Save() calls GDI+ internally. I verified with debugger. My memory DC is compatible with my window DC thus the image is 32 BPP. And GDI+ sets all pixels alpha values to 0. But I want all pixels alpha value to be 0xFF, because then the image can be loaded into a CMFCToolbar object. – Christoph Thien Jul 05 '18 at 05:07
  • OK I forgot about that. Looking at `CImage::Save()` source, it checks `m_bHasAlphaChannel` (which is set by your `SetHasAlphaChannel()` call) and creates the GDI+ bitmap using `PixelFormat32bppARGB`. So that should preserve any alpha channel data you have. You only need to set the alpha channel values before calling your `Save()` function. – zett42 Jul 05 '18 at 13:08
  • `SetHasAlphaChannel` sets a flag and creates a 32-bit image at the end, or it is used for drawing with transparency, but it doesn't affect alpha which may remain at zero. – Barmak Shemirani Jul 05 '18 at 18:42

1 Answers1

3

Use GetDIBits to read 32-bit pixel data, and loop through the bits to set alpha to 0xFF.

bool Save(CBitmap *bitmap)
{
    if(!bitmap)
        return false;

    BITMAP bm;
    bitmap->GetBitmap(&bm);
        if(bm.bmBitsPixel < 16)
            return false;

    DWORD size = bm.bmWidth * bm.bmHeight * 4;
    BITMAPINFOHEADER bih = { sizeof(bih), bm.bmWidth, bm.bmHeight, 1, 32, BI_RGB };
    BITMAPFILEHEADER bfh = { 'MB', 54 + size, 0, 0, 54 };

    CClientDC dc(0);
    std::vector<BYTE> vec(size, 0xFF);
    int test = GetDIBits(dc, *bitmap, 0, bm.bmHeight, &vec[0],
            (BITMAPINFO*)&bih, DIB_RGB_COLORS);
    for(DWORD i = 0; i < size; i += 4)
        vec[i + 3] = 0xFF;

    CFile fout;
    if(fout.Open(filename, CFile::modeCreate | CFile::modeWrite))
    {
        fout.Write(&bfh, sizeof(bfh));
        fout.Write(&bih, sizeof(bih));
        fout.Write(&vec[0], size);
        return true;
    }

    return false;
}


As an alternative (but I am not sure if this is reliable) initialize the memory with 0xFF. GetDIBits will set the RGB part but won't overwrite the alpha values:
std::vector<BYTE> vec(size, 0xFF);
GetDIBits...


Or using GDI+
bool Save(CBitmap *bitmap)
{
    if(!bitmap)
        return false;

    BITMAP bm;
    bitmap->GetBitmap(&bm);
    if(bm.bmBitsPixel < 16)
        return false; //needs palette

    Gdiplus::GdiplusStartupInput tmp;
    ULONG_PTR token;
    Gdiplus::GdiplusStartup(&token, &tmp, NULL);

    Gdiplus::Bitmap *src = Gdiplus::Bitmap::FromHBITMAP(*bitmap, NULL);
    Gdiplus::Bitmap *dst = src->Clone(0, 0, src->GetWidth(), src->GetHeight(), 
        PixelFormat32bppARGB);

    LPCOLESTR clsid_bmp = L"{557cf400-1a04-11d3-9a73-0000f81ef32e}";
    CLSID clsid;
    CLSIDFromString(clsid_bmp, &clsid);
    bool result = dst->Save(L"file.bmp", &clsid) == 0;
    delete src;
    delete dst;

    Gdiplus::GdiplusShutdown(token);

    return result;
}
Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77