如何绘制32位透明通道位图?

13

我需要创建一个自定义控件来显示带有alpha通道的bmp图像。背景可以用不同的颜色绘制,图像具有阴影,因此我需要真正地“绘制”alpha通道。

有人知道如何做吗?

如果可能的话,我还想创建一个掩模,使用alpha通道信息来确定鼠标是点击了图像还是透明区域。

非常感谢任何形式的帮助!

谢谢。

编辑(JDePedro):正如你们中的一些人建议的那样,我一直在尝试使用Alpha混合来绘制具有Alpha通道的位图。这只是一个测试,我加载了一个32位位图资源,并尝试使用AlphaBlend函数进行绘制:

void CAlphaDlg::OnPaint()
{
    CClientDC dc(this);
    CDC  dcMem;
    dcMem.CreateCompatibleDC(&dc);

    CBitmap bitmap;
    bitmap.LoadBitmap(IDB_BITMAP);

    BITMAP BitMap;
    bitmap.GetBitmap(&BitMap);
    int nWidth = BitMap.bmWidth;
    int nHeight = BitMap.bmHeight;
    CBitmap *pOldBitmap = dcMem.SelectObject(&bitmap);

    BLENDFUNCTION m_bf;
    m_bf.BlendOp = AC_SRC_OVER;
    m_bf.BlendFlags = 0;
    m_bf.SourceConstantAlpha = 255;
    m_bf.AlphaFormat = AC_SRC_ALPHA;
    AlphaBlend(dc.GetSafeHdc(), 100, 100, nWidth, nHeight, dcMem.GetSafeHdc(), 0, 0,nWidth, nHeight,m_bf); 

    dcMem.SelectObject(pOldBitmap);

    CDialog::OnPaint();
}

这只是一个测试,所以我将代码放在对话框的 OnPaint 中(我还尝试了 CDC 对象的 AlphaBlend 函数)。

非透明区域被正确绘制,但位图应该是透明的地方,出现了白色。

有什么帮助吗???

这是一个截图...不容易看到,但是蓝色圆圈周围有一个白色矩形:alt text http://img385.imageshack.us/img385/7965/alphamh8.png

好的。我懂了!我必须为每个像素的 alpha 值预先乘以它的值。有人可以建议一种优化的方法吗?

6个回答

13

对于未来的谷歌用户,这里有一个可行的预乘函数。请注意,此代码摘自http://www.viksoe.dk/code/alphatut1.htm

inline void PremultiplyBitmapAlpha(HDC hDC, HBITMAP hBmp)
{
   BITMAP bm = { 0 };
   GetObject(hBmp, sizeof(bm), &bm);
   BITMAPINFO* bmi = (BITMAPINFO*) _alloca(sizeof(BITMAPINFOHEADER) + (256 * sizeof(RGBQUAD)));
   ::ZeroMemory(bmi, sizeof(BITMAPINFOHEADER) + (256 * sizeof(RGBQUAD)));
   bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
   BOOL bRes = ::GetDIBits(hDC, hBmp, 0, bm.bmHeight, NULL, bmi, DIB_RGB_COLORS);
   if( !bRes || bmi->bmiHeader.biBitCount != 32 ) return;
   LPBYTE pBitData = (LPBYTE) ::LocalAlloc(LPTR, bm.bmWidth * bm.bmHeight * sizeof(DWORD));
   if( pBitData == NULL ) return;
   LPBYTE pData = pBitData;
   ::GetDIBits(hDC, hBmp, 0, bm.bmHeight, pData, bmi, DIB_RGB_COLORS);
   for( int y = 0; y < bm.bmHeight; y++ ) {
      for( int x = 0; x < bm.bmWidth; x++ ) {
         pData[0] = (BYTE)((DWORD)pData[0] * pData[3] / 255);
         pData[1] = (BYTE)((DWORD)pData[1] * pData[3] / 255);
         pData[2] = (BYTE)((DWORD)pData[2] * pData[3] / 255);
         pData += 4;
      }
   }
   ::SetDIBits(hDC, hBmp, 0, bm.bmHeight, pBitData, bmi, DIB_RGB_COLORS);
   ::LocalFree(pBitData);
}

那么你的OnPaint应该是这样的:

void MyButton::OnPaint()
{
    CPaintDC dc(this);

    CRect rect(0, 0, 16, 16);

    static bool pmdone = false;
    if (!pmdone) {
        PremultiplyBitmapAlpha(dc, m_Image);
        pmdone = true;
    }

    BLENDFUNCTION bf;
    bf.BlendOp = AC_SRC_OVER;
    bf.BlendFlags = 0;
    bf.SourceConstantAlpha = 255;
    bf.AlphaFormat = AC_SRC_ALPHA;

    HDC src_dc = m_Image.GetDC();
    ::AlphaBlend(dc, rect.left, rect.top, 16, 16, src_dc, 0, 0, 16, 16, bf);
    m_Image.ReleaseDC();
}

并且图片的加载(在你的控件构造函数中):

if ((HBITMAP)m_Image == NULL) {
    m_Image.LoadFromResource(::AfxGetResourceHandle(), IDB_RESOURCE_OF_32_BPP_BITMAP);
}

如果我想让透明图片显示在静态标签上怎么办? pStatic->ModifyStyle(0xF, SS_BITMAP);
pStatic->SetBitmap(bitmap);
- Michael Haephrati
在OnPaint()中使用静态变量肯定是一个非常糟糕的想法。如果你有多个按钮,这将失败。pmdone应该是每个MyButton的成员变量。然而,BLENDFUNCTION可以是静态的。或者更好的方法是:在LoadFromResource()之后立即调用PremultiplyBitmapAlpha()并使用GetWindowDC()获取任何窗口的DC。然后你就不需要变量pmdone了。 - Elmue
m_Image也应该是静态的,显然。没有必要为每个按钮加载相同的位图。而且你不能在LoadFromResource()之后调用它(至少不是按照这里呈现的方式),因为你可能在创建任何窗口之前实例化这些对象。 - Roel

7
我通常使用DIBSection来完成这个操作 - 它是一种设备无关位图,您可以直接修改像素。不幸的是,MFC没有对DIBSections提供支持:您必须使用Win32函数CreateDIBSection()来使用它。
首先以32位RGBA格式(即每个像素四个字节:一个红色、一个绿色、一个蓝色和一个透明通道)加载位图。在控件中创建一个适当大小的DIBSection。然后,在绘制例程中:
  • 将位图数据复制到DIBSection的位图数据中,使用alpha通道字节将位图图像与背景颜色混合。
  • 创建设备上下文并将DIBSection选择到其中。
  • 使用BitBlt()从新设备上下文复制到绘制设备上下文中。
您可以通过查看alpha通道值来创建给定原始位图数据的掩码 - 我不确定您在这里询问的是什么。

1

你走在正确的道路上,但需要修复两个问题。

第一,使用 ::LoadImage( .. LR_CREATEDIBSECTION ..) 而不是 CBitmap::LoadBitmap。其次,您必须将位图中每个像素的 RGB 值“预乘”为其相应的 A 值。这是 AlphaBlend 函数的要求,请参阅 此 MSDN 页面 上的 AlphaFormat 描述。

lpng 有一个有效的代码,可以对 DIB 数据进行预乘处理。


1

你需要使用alpha blend与背景颜色混合,然后去掉 alpha 通道将其绘制到控件上。

alpha 通道应该是图像中每四个字节的一个。你可以直接使用它作为遮罩,或者将每四个字节复制到一个新的遮罩图像中。


嗨,马克,让我们看看我是否理解了你的建议。想象一下,我从CStatic/CWnd派生我的控件,并添加一个CBitmap作为成员。然后每次控件将要被绘制(在OnPaint方法中),我都必须获取背景并与我的图像混合吗? - Javier De Pedro
我是对的吗?我必须自己做所有事情吗? 这似乎会有很高的性能成本... 你(任何人)能告诉我最好的方法(更优)吗?也许有一些好的文章或代码片段?谢谢。 - Javier De Pedro

1

1

一种优化的预乘RGB通道与alpha通道的方法是设置一个[256][256]的数组,其中包含计算出的乘法结果。第一维是alpha值,第二维是R/G/B值,数组中的值是所需的预乘值。

正确设置了这个数组后,您可以按以下方式计算所需的值:

R = multiplicationLookup[alpha][R];
G = multiplicationLookup[alpha][G];
B = multiplicationLookup[alpha][B];

不确定随机访问一个64 KiB大数组是否比执行计算更快。特别是当操作可以完美地分布在矢量单元上时。这肯定会给CPU缓存带来不必要的压力,而回报却很少(如果有的话)。 - IInspectable

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