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

我真的需要一些帮助。 我正在尝试将一个我认为32bpp的图像加载到MenuItem上的预乘alpha( 我按照本指南在GIMP中制作图像 )。 我知道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); } 

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

代码结果

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

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

之前

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

后

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

花边

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

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

任何帮助是极大的赞赏。

如果将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上禁用主题,结果可能很丑:

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

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

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

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

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

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

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

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

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

在Photoshop中:

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

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

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

我认为你应该坚持用GDI +加载图像,因为这确实让你得到alpha位 – 你只需要一个手动方法将这些位放入HBITMAP而不会丢失它们。

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

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