【问题标题】:How to draw 32-bit alpha channel bitmaps?如何绘制 32 位 alpha 通道位图?
【发布时间】:2010-09-23 08:51:23
【问题描述】:

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

有人知道怎么做吗?

如果可能的话,我还希望使用 Alpha 通道信息创建一个遮罩,以了解鼠标是点击了图像还是透明区域。

任何形式的帮助将不胜感激!

谢谢。

Edited(JDePedro):正如你们中的一些人所建议的那样,我一直在尝试使用 alpha blend 来绘制带有 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 值预乘每个像素。有人可以建议优化的方法吗?

【问题讨论】:

    标签: mfc alphablending bmp


    【解决方案1】:

    对于未来的谷歌用户,这是一个有效的预乘函数。请注意,这是从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(位图);
    • OnPaint() 中的静态变量肯定是一个非常糟糕的主意。如果您有多个按钮,这将失败。 pmdone 应该是每个 MyButton 的成员变量。然而, BLENDFUNCTION 可能是静态的。或者更好:您可以在 LoadFromResource() 之后立即调用 PremultiplyBitmapAlpha() 并将任何窗口的 DC 与 GetWindowDC() 一起使用。那么你就不再需要变量 pmdone 了。
    • m_Image 显然也应该是静态的。无需为每个按钮加载相同的位图。不,您不能在 LoadFromResource() 之后调用它(至少不是以此处显示的方式),因为您可能在创建任何窗口之前实例化这些对象。
    【解决方案2】:

    我通常这样做的方式是通过 DIBSection - 一个独立于设备的位图,您可以直接修改其像素。不幸的是,没有任何 MFC 支持 DIBSection:您必须使用 Win32 函数 CreateDIBSection() 才能使用它。

    首先将位图加载为 32 位 RGBA(即每个像素四个字节:一个红色、一个绿色、一个蓝色和一个用于 Alpha 通道)。在控件中,创建一个适当大小的 DIBSection。然后,在绘画例程中

    • 将位图数据复制到 DIBSection 的位图数据中,使用 Alpha 通道字节将位图图像与背景颜色混合。
    • 创建一个设备上下文并在其中选择 DIBSection。
    • 使用 BitBlt() 从新设备上下文复制到绘制设备上下文。

    您可以简单地通过查看 Alpha 通道值来创建一个给定原始位图数据的蒙版 - 我不确定您在这里问的是什么。

    【讨论】:

      【解决方案3】:

      你需要用你的背景颜色做一个alpha blend,然后取出alpha通道将它绘制到控件上。

      Alpha 通道应该是图像的每 4 个字节。您可以直接将它用于您的掩码,或者您可以将每 4 个字节复制到一个新的掩码图像。

      【讨论】:

      • 嗨,马克,让我们看看我是否理解您的建议。想象一下,我从 CStatic/CWnd 派生了我的控制权,并添加了一个 CBitmap 作为成员。然后每次绘制控件时(在 OnPaint 方法中)我都必须获取背景并与我的图像混合?
      • (继续)我说的对吗?我必须自己做所有事情吗?似乎它可以有很高的性能成本......你(任何人)能告诉我最好的方法(更优化)吗?也许一些上帝的文章甚至coden-p?谢谢。
      【解决方案4】:

      使用AlphaBlend function 绘制它非常容易。

      至于您的掩码,您需要get the bits 位图并检查您感兴趣的每个像素的 Alpha 通道字节。

      【讨论】:

        【解决方案5】:

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

        正确设置此数组后,您可以像这样计算所需的值:

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

        【讨论】:

        • 值得怀疑,随机访问 64 KiB 大数组是否比执行计算更快。尤其是当操作可以如此完美地分布在向量单元中时。它肯定会给 CPU 缓存带来过大的压力,而回报却很少(如果有的话)。
        【解决方案6】:

        你在正确的轨道上,但需要解决两件事。

        首先使用 ::LoadImage( .. LR_CREATEDIBSECTION ..) 而不是 CBitmap::LoadBitmap。第二,您必须将位图中每个像素的 RGB 值“预乘”到它们各自的 A 值。这是 AlphaBlend 功能的要求,请参阅 this MSDN page 上的 AlphaFormat 说明。 T

        lpng 有一个可以对 DIB 数据进行预乘的工作代码。

        【讨论】:

          猜你喜欢
          • 2011-02-06
          • 2014-03-12
          • 2013-08-28
          • 2012-03-28
          • 2012-05-30
          • 1970-01-01
          • 2023-04-07
          • 2013-07-07
          • 1970-01-01
          相关资源
          最近更新 更多