65.9K
CodeProject 正在变化。 阅读更多。
Home

透明位图按钮和 .NET 中的 Alpha 混合区域

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (33投票s)

2004年8月13日

CPOL

4分钟阅读

viewsIcon

278495

downloadIcon

3098

如何在 .NET Forms 中创建和绘制透明位图按钮。

Sample Image - TransButtonNetDemo.jpg

引言

自从我为 MFC 创建了 alpha 混合位图按钮以来,我想在 .NET Windows Forms 环境中尝试同样的事情。尽管代码背后的概念非常相似,但我发现实现细节要更具挑战性。因此,我认为分享一些实现这一目标所需的陷阱和解决方法可能会使其他人受益。

我没有为此示例创建一个非常复杂的演示项目。位图背景窗口上只有几个按钮用于显示示例,并且只有刷新按钮和退出按钮实际上执行操作。刷新按钮将更改背景位图(共 2 个),以便透明按钮有不同的外观。

创建位图

此按钮的位图必须包含四个图像,水平排列在位图内。这四个状态依次代表 Normal、Selected、Hover 和 Disable。这四个图像必须大小相同。我为此演示使用了 48x48 的圆形按钮和 75x48 的药丸形按钮。

按钮位图是每像素 32 位 (32bpp) 的 RGB 图像,带有决定透明度的 alpha 通道。我在 Photoshop 中创建这些图像的方式与 MFC 按钮图像略有不同。棘手的部分是 alpha 通道,因为 Photoshop 并不总是非常清楚如何正确地获得它。我采取的步骤是,首先通过选择“图层-通道-路径”窗口中的“通道”选项卡来创建一个新的 alpha 通道,然后单击底部的“创建新通道”小按钮。创建 Alpha 通道后,我双击该通道以打开“选项”对话框,然后将“不透明度”更改为 0%,并使用黑色或白色颜色和“蒙版区域”选择。接下来,我关闭 RGB 通道视图(取消选中每个通道旁边的眼睛图标),并保持 Alpha 通道可见。此时的显示应为黑色或白色,具体取决于蒙版颜色。然后,我通过 Ctrl+单击 RGB 通道来选择 RGB 通道。这应该会选择实际按钮图层的部分或全部。最后,我用黑色或白色画笔绘制不透明区域,以获得所需的透明度级别。显示通常看起来像这样

Photoshop Alpha Channel painting

图像必须保存为 32 位 ARGB,因此 Alpha 图层必须在“保存”对话框中保留,请记住只能有一个 Alpha 图层。

从资源 DLL 加载位图

关于 GDI+ 中的 alpha 通道位图,我学到的最令人沮丧的事情之一是它们无法正确加载。无论我尝试什么,alpha 通道总是丢失,位图被转换回 32bppRGB。当我偶然发现 Steve McMahon 在 vbaccelerator.com 上的一个 演示时,我终于找到了解决此问题的方法。Steve 的演示通过从资源 DLL 加载位图并进行一些操作来保持 alpha 数据完整,提供了一个很好的解决方法。

public static Bitmap DibToBitmap(
    IntPtr hDib
    )
{
    BITMAP tBM = new BITMAP();
    GetObjectBitmap(hDib, Marshal.SizeOf(tBM), ref tBM);
    Bitmap bm = new Bitmap(tBM.bmWidth, tBM.bmHeight);

    // set the bitmap's data to the data from
    // the DIB:
    if (tBM.bmBitsPixel == 32)
    {
        // Bizarre but true: you *must* clone the newly created
        // bitmap to get one with the correct pixel format, even
        // if you attempted to create the original one with the 
        // correct format...
        bm = bm.Clone(new Rectangle(0, 0, tBM.bmWidth, tBM.bmHeight),
            PixelFormat.Format32bppArgb);

        // Lock the bitmap bits
        BitmapData destData = bm.LockBits(
            new Rectangle(0, 0, bm.Width, bm.Height),
            ImageLockMode.ReadWrite,
            PixelFormat.Format32bppArgb);
        int destWidth = destData.Stride;
        IntPtr destScan0 = destData.Scan0;

        unsafe
        {
            byte * pDest = (byte *) (void *) destScan0;
            // The DIB is upside down compared to a GDI+ bitmap
            pDest += ((bm.Width * 4) * (bm.Height - 1));
            byte * pSrc = (byte *) (void *) tBM.bmBits;

            for (int y = 0; y < bm.Height; ++y)
            {
                for (int x = 0; x < bm.Width; ++x)
                {
                    pDest[0] = pSrc[0]; // blue
                    pDest[1] = pSrc[1]; // green
                    pDest[2] = pSrc[2]; // red
                    pDest[3] = pSrc[3]; // alpha
                    
                    // Move to next BGRA
                    pDest += 4;
                    pSrc += 4;
                }
                pDest -= (bm.Width * 8);
            }
        }

        bm.UnlockBits(destData);
    }
    else
    {
        // Easier to just copy src -> dst using GDI.

        // Put the DIB into a DC:
        IntPtr hWndDesktop = GetDesktopWindow();
        IntPtr hDCComp = GetDC(hWndDesktop);
        IntPtr hDCSrc = CreateCompatibleDC(hDCComp);
        ReleaseDC(hWndDesktop, hDCComp);
        IntPtr hBmpOld = SelectObject(hDCSrc, hDib);

        Graphics gfx = Graphics.FromImage(bm);
        IntPtr hDCDest = gfx.GetHdc();
        BitBlt(hDCDest, 0, 0, tBM.bmWidth, tBM.bmHeight, hDCSrc, 0, 0, SRCCOPY);
        gfx.ReleaseHdc(hDCDest);

        SelectObject(hDCSrc, hBmpOld);
        DeleteDC(hDCSrc);
    }

    return bm;

}

利用 Steve 的想法,我创建了一个 MFC DLL,将所有 32bppARGB 位图加载到其中,并使用他的 ResourceLibraryImageUtility 类来创建 GDI+ Bitmap 对象。

    // Resource file created as MFC dll to store 32bpp
    // alpha channel bitmaps.
    string ResourceFileName = "TransButtonResources.dll";

    if (File.Exists(ResourceFileName))
    {
        using (ResourceLibrary lib = new ResourceLibrary())
        {
            lib.Filename = ResourceFileName;
            if (!lib.Handle.Equals(IntPtr.Zero))
            {
                // Load bitmaps from resource file with specified ids
                bgImage1 = LoadBitmapResource(lib, 2000);
                bgImage2 = LoadBitmapResource(lib, 2006);
                purpleButton  = LoadBitmapResource(lib, 2001);
                redButton = LoadBitmapResource(lib, 2002);
                whiteButton = LoadBitmapResource(lib, 2003);
                openButton = LoadBitmapResource(lib, 2004);
                saveButton = LoadBitmapResource(lib, 2005);
            }
        }
    }

按钮类

我这里的 TransButton 类并不花哨。实际上,我从 XP 按钮示例中借用了控件状态代码。我添加了设置位图和区域的函数,并绘制了来自 4 状态位图中水平偏移量的正确图像。

    if (ButtonImages != null)
    {
        Rectangle destRect = new Rectangle( 0, 0, 
            pea.ClipRectangle.Width, pea.ClipRectangle.Height);
        Rectangle srcRect = new Rectangle( xOffset, yOffset, 
            pea.ClipRectangle.Width, pea.ClipRectangle.Height);
        GraphicsUnit units = GraphicsUnit.Pixel;
        pea.Graphics.DrawImage(ButtonImages, destRect, srcRect, units);
    }

GDI+ 非常好的一点是,我不再需要进行逐像素的 alpha 混合。

Using the Code

如果您创建一个新项目并使用这里的类,请记住这些类中存在非托管代码,因此必须更改项目设置以允许不安全代码。

请按照以下步骤创建并显示您的透明按钮

  1. 以 32 位 ARGB 格式创建按钮。
  2. 创建一个资源 DLL 并导入位图。
  3. 将资源 DLL 移动到您的 .NET 项目目录,或提供引用路径。
  4. 在您的 .NET 项目的 Form 上创建具有透明背景色的按钮。
  5. 将按钮类类型更改为 TransButton.TransButton
  6. 使用 ResourceLibrary 类代码从 DLL 提取位图。
  7. InitializeComponent 之后设置按钮的 Bitmap 和 Region。

就这些了。

© . All rights reserved.