【问题标题】:How to set alpha value for all pixels in a bitmap using MFC or GDI or GDI+如何使用 MFC 或 GDI 或 GDI+ 为位图中的所有像素设置 alpha 值
【发布时间】:2018-12-13 01:03:53
【问题描述】:

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

到目前为止,我发现这段代码最优雅:

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

生成的文件是 32 BPP 颜色空间,所有 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 - 不是吗?
  • 尝试使用 GDI+ 进行保存,它可以很好地处理 alpha 通道。 CImage 只是 HBITMAP 的包装器,它没有任何 alpha 通道的概念。
  • CImage.Save() 在内部调用 GDI+。我用调试器验证了。我的内存 DC 与我的窗口 DC 兼容,因此图像为 32 BPP。 GDI+ 将所有像素的 alpha 值设置为 0。但我希望所有像素的 alpha 值都为 0xFF,因为这样可以将图像加载到 CMFCToolbar 对象中。
  • 好吧,我忘了。查看CImage::Save() 源,它检查m_bHasAlphaChannel(由您的SetHasAlphaChannel() 调用设置)并使用PixelFormat32bppARGB 创建GDI+ 位图。因此,这应该保留您拥有的任何 Alpha 通道数据。您只需要在调用 Save() 函数之前设置 alpha 通道值。
  • SetHasAlphaChannel 设置一个标志并在最后创建一个 32 位图像,或者用于绘制透明,但不影响可能保持为零的 alpha。

标签: winapi mfc gdi+ gdi cmfctoolbar


【解决方案1】:

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

【讨论】:

  • 这样就行了。有趣的方法。非常感谢。但我最终需要手动创建位图和手动写入文件。如果可能的话,我想避免这种情况。
  • 然后按照前面的建议使用 GDI+。
  • 手动旋转几个像素没什么问题,这可能是最快的方法。 Tiny nitpick:为什么要预先填写 vec
  • 使用 GDI+ 我到此结束:void Save(CBitmap * bitmap) { Gdiplus::Bitmap bm((HBITMAP)pcBitmap-&gt;GetSafeHandle(), NULL); bm.Save(pwszFileName, &amp;clsidEncoder, NULL); } 仍然所有像素的 alpha 值都是 0。在 bm.Save(...) 之前必须有一些改变 alpha 值的转换。
  • @ChristophThien 查看更新。确保 GDI+ 已初始化。检查返回值是否有错误。
猜你喜欢
  • 1970-01-01
  • 2011-01-31
  • 2013-07-22
  • 2012-12-08
  • 2011-08-29
  • 1970-01-01
  • 2020-07-10
  • 2011-09-01
  • 2010-11-13
相关资源
最近更新 更多