将图像加载到MenuItem上会使预乘的alpha图像失去透明度

时间:2013-10-11 16:26:32

标签: c# winapi contextmenu alpha gimp

我真的需要一些帮助。我正在尝试将一个我认为是32bpp的图像加载到MenuItemI followed this guide to make the image in GIMP)上的预乘alpha。我知道ContextMenuStrip类,不想使用它。

以下是我用于将图像设置到MenuItem上的代码:

// apis
[DllImport("user32.dll", SetLastError = true)]
static extern bool SetMenuItemInfo(IntPtr hMenu, uint uItem, bool fByPosition,
                                   [In] ref MENUITEMINFO lpmii);

[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern IntPtr LoadImage(IntPtr hinst, string lpszName, uint uType,
                               int cxDesired, int cyDesired, uint fuLoad);

// structures
[StructLayout(LayoutKind.Sequential)]
struct MENUITEMINFO
{
    public uint cbSize;
    public uint fMask;
    public uint fType;
    public uint fState;
    public uint wID;
    public IntPtr hSubMenu;
    public IntPtr hbmpChecked;
    public IntPtr hbmpUnchecked;
    public IntPtr dwItemData;
    public string dwTypeData;
    public uint cch;
    public IntPtr hbmpItem;
}

// constants
private const uint LR_LOADFROMFILE = 0x10u;
private const uint IMAGE_BITMAP = 0x0u;
private const uint MIIM_BITMAP = 0x80u;

// points the to the image below in the preview of GIMP
private const string IMAGE_PATH = @"C:\Test\Images\premultalpha.bmp";

// methods
private void SetMenuItemImage()
{

    // get the hbitmap for the image
    // i am assuming that the alpha channel is preservered on this call
    IntPtr hbitmap = LoadImage(IntPtr.Zero, IMAGE_PATH, 
                               IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);

    // create the menuiteminfo structure
    MENUITEMINFO mii = new MENUITEMINFO();

    mii.cbSize = (uint)Marshal.SizeOf(typeof(MENUITEMINFO));

    // retrieves or sets the hbmpItem member
    mii.fMask = MIIM_BITMAP;

    // handle to the bitmap displayed
    mii.hbmpItem = hbitmap;

    // returns true
    SetMenuItemInfo(this.ContextMenu1.Handle, 0, true, ref mii);
}

这是使用我的图像的代码的结果:

Result of code

这里显而易见的问题是没有透明度,而是有黑色背景。

这是GIMP中的图像在遵循指南之后在保存和重新打开之前制作预乘Alpha通道

Before

这是保存和重新打开后GIMP 中的图像:

After

我注意到我再也看不到图片之前版本的alpha通道掩码了。我不确定它是否与我在尝试将之前的图片保存为.bmp时获得的此消息有关:

Gimp

很抱歉这是一个很长的帖子,但我想尽我所能提供所有信息。我不确定我的问题是关于MenuItem的透明度。我被告知,如果你加载一个32bpp和预乘alpha的位图,透明度就可以了。

我知道我无法使用托管方法Bitmap.Gethbitmap(),因为它丢失了Alpha通道。这就是我为了保留它而使用LoadImage winapi调用的原因。

非常感谢任何帮助。

2 个答案:

答案 0 :(得分:6)

如果将LR_CREATEDIBSECTION标志添加到LoadImage调用的最后一个参数,它将按原样加载BMP资源,包括任何alpha通道(如果它是32位BMP文件)。当图像转换为兼容的位图时,做其他任何事情似乎都会失去alpha通道(即使桌面设置为32bpp)。

如果BMP文件本身是正确的,那么当使用MIIM_BITMAP和hbmpItem分配给菜单项时,DIB似乎工作正常。

当启用主题时,这适用于Vista到Windows 8.1。

当禁用主题时,这并不总是有效。(请注意,即使在Windows 8中,即使用户无法再在系统范围内禁用主题,也可以禁用主题。)

(有时它也适用于主题已禁用,老实说我不能肯定为什么。它在我的主要Win7x64机器上有和没有主题,但在测试虚拟机也运行Win7x64它看起来很糟糕主题禁用。我怀疑它取决于哪些其他shell扩展添加了菜单,并且如果第三方扩展恰好使用所有者绘制图标,它将shell翻转到另一个代码路径,这使得新的hbmpItem方法如果没有预设,默认情况下也不会有效。但这只是一个猜测。不一致似乎是Windows中的一个错误,并且不太可能被修复,因为它存在于Vista到Windows 8.1你只是必须避免在禁用主题时使用它,即使在Windows 7或8上也是如此。)

无论是否启用主题,这在Windows XP上都无法正常运行

在XP和更新版本的Windows上禁用主题,结果可能很丑:

  • 图标向右移动太远,位于与菜单上大多数其他图标不同的位置,并且由于菜单行高度较小,因此16x16图标看起来很好并且与其他图标一致主题菜单在未经授权的菜单中会过大。

  • 当提供32bpp HBITMAP时,XP也会忽略alpha通道,因此您将获得一个带有纯黑色背景的图标。

  • 因此,如果您关心这些情况,那么您必须回退并且不在旧的或未经授权的系统上提供图标,或提供替代图标。 (将其设置为13x13,并在运行时自行将其与系统菜单颜色混合。)您还需要使用hbmpUnchecked字段而不是hbmpItem,以便图标显示在所需的位置。

  • 奖励:好像这一切都不够糟糕,主题检测API很乱,可能会告诉您系统范围的主题,但不会告诉您应用程序是否已禁用主题本身或已被禁用表现为使用旧的comctl32.dll。除了Windows XP的测试之外,这似乎是一个很好的检查:

    (IsThemeActive() && IsAppThemed() && (GetThemeAppProperties() & STAP_ALLOW_CONTROLS))

    您可能还想使用comctl32.dll DllGetVersion export来确定应用程序是否使用comctl32.dll版本6或更高版本(这意味着支持主题,但不会告诉您它们是启用还是禁用),但我不确定是否需要它。

回到启用主题但我们不在XP上的主要案例:

  • 如果您在菜单中看到一些透明度但有奇怪的点和其他文物等问题,或者当您将鼠标移到菜单项上时,点会消失并且图标背景变为白色,则表示Alpha通道不正确。它需要预乘alpha,预乘黑色。

我没有安装GIMP,所以我无法帮助,但以下是使用Photoshop创建BMP文件的步骤,以防他们帮助某人。

在Photoshop中:

  • 将源图像视为PNG。
  • 在“频道”标签上,添加一个新图层(它应自动称为“Alpha”)。
  • 用黑色填充整个Alpha通道。
  • 返回图层选项卡并 ctrl - 点击主图层(它应该是到目前为止唯一的图层)来选择其透明蒙版。
  • 返回“频道”标签,然后点击Alpha频道。您在上一步中所做的选择仍应处于活动状态;用白色填充该选项。
  • (此时,您有一个带有显式Alpha通道的非预乘alpha图像。)
  • 现在返回“图层”标签,然后返回主图层后面的新图层。用黑色填充。
  • 最后,将文件另存为32-bpp的BMP。 (确保在Photoshop的“另存为”对话框中勾选Alpha通道复选框。)

在添加背景图层之前制作的Alpha通道与黑色背景图层的组合会导致预乘alpha,与黑色预乘。

答案 1 :(得分:1)

这种方法的问题是LoadImage()也不支持alpha。

我认为你应该坚持使用GDI +加载图像,因为这样可以让你获得alpha位 - 你只需要一个手动方法将这些位变成HBITMAP而不会丢失它们。

我不太了解.NET,断言它不支持这一点,但我找不到一个快速搜索的简单解决方案。所以我认为最好的办法是使用Bitmap.LockBits来访问原始数据,然后通过CreateDIBSection()使用pinvoke并自行将这些位复制到DIB部分。

如果源位图和目标位图的大小相同,那么它应该只需要一个memcpy()或等效的一次复制所有位图数据(x * y * 4字节)。