如何使用MFC或GDI或GDI+为位图中的所有像素设置Alpha值

3

我是在一个MFC应用程序中。我使用内存DC创建了一个位图,现在想将其保存为DIB文件。

我发现以下代码是目前最优雅的:

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

生成的文件是32位色彩空间,所有alpha值均设置为“0”。

现在我想将位图用作工具栏位图:

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

但所有图标都消失了。

在导入位图时,MFC内部调用PreMultiplyAlpha()函数。然后所有像素的RGB分量都为'0'。实际上整个位图被清零了。

我该如何在保存之前将每个像素的alpha值设置为'0xFF'?

我尝试过:

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

但这仅影响像素的RGB值。

到目前为止,我一直抵制迭代每个像素并修改位图内存的做法。我正在寻求一种优雅的解决方案。也许只需要一行代码就能实现。


你想要的是0xff的alpha值,而不是0 - 对吧? - noelicus
2
尝试使用GDI+来保存,它能很好地处理Alpha通道。CImage只是一个HBITMAP的包装器,没有任何关于Alpha通道的概念。 - zett42
CImage.Save() 内部调用 GDI+。我通过调试器验证了这一点。我的内存 DC 与窗口 DC 兼容,因此图像是 32 BPP。而 GDI+ 将所有像素的 alpha 值设置为 0。但我希望所有像素的 alpha 值都为 0xFF,因为这样图像才能加载到 CMFCToolbar 对象中。 - Christoph Thien
好的,我忘记了。查看CImage::Save()源代码,它检查m_bHasAlphaChannel(这是由您的SetHasAlphaChannel()调用设置的),并使用PixelFormat32bppARGB创建GDI+位图。因此,它应该保留您拥有的任何alpha通道数据。在调用您的Save()函数之前,您只需要设置alpha通道值即可。 - zett42
SetHasAlphaChannel 设置一个标志并在最后创建一个32位图像,或者用于带有透明度的绘制,但它不影响可能仍为零的 alpha。 - Barmak Shemirani
1个回答

3
使用 GetDIBits 读取32位像素数据,循环遍历位并将 alpha 设置为 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;
}

作为替代方案(但我不确定这是否可靠),使用0xFF初始化内存。 GetDIBits将设置RGB部分,但不会覆盖alpha值:
std::vector<BYTE> vec(size, 0xFF);
GetDIBits...

或使用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;
}

那就行了。有趣的方法。非常感谢。但是最终我还是需要手动创建位图和手动写入文件。如果可能的话,我想避免这种情况。 - Christoph Thien
然后像之前建议的那样使用GDI+。 - Barmak Shemirani
手动调整几个像素没有问题,这可能是最快的方法。微小的挑剔:为什么要预先填充 vec - Paul Sanders
使用GDI+,我最终到达了这里:void Save(CBitmap * bitmap) { Gdiplus::Bitmap bm((HBITMAP)pcBitmap->GetSafeHandle(), NULL); bm.Save(pwszFileName, &clsidEncoder, NULL); } 然而所有像素的alpha值都是0。在bm.Save(...)之前必须进行某些转换以改变alpha值。 - Christoph Thien
@ChristophThien 请查看更新。确保GDI+已初始化。检查错误的返回值。 - Barmak Shemirani
@PaulSanders 您是正确的,我刚刚删除了初始化。 - Barmak Shemirani

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接