1

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

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

代码结果

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

这是在保存和重新打开之前按照指南制作预乘 Alpha 通道后图像在 GIMP 中的样子:

前

这是保存并重新打开图像在 GIMP 中的样子:

后

我注意到我再也看不到图片之前版本上的 Alpha 通道蒙版。我不确定这是否与我尝试将之前的图片保存为 .bmp 时收到的这条消息有关:

瘸子

抱歉,这篇文章太长了,但我正在尽力提供所有我能提供的信息。我不确定我的问题是关于 MenuItem 的透明度。有人告诉我,如果您加载具有 32bpp 和预乘 alpha 的位图,则透明度会正常工作。

我知道我不能使用托管方法Bitmap.Gethbitmap(),因为它丢失了 alpha 通道。这就是为什么我改为使用LoadImagewinapi 调用以希望保留它。

任何帮助是极大的赞赏。

4

2 回答 2

8

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

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

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

当主题被禁用时,这并不总是很好。(请注意,即使在 Windows 8 中,主题也可以被应用程序禁用,即使用户不能再在系统范围内禁用它们。)

(有时它在禁用主题的情况下也可以正常工作,老实说,我不能确定为什么。它在我的主 Win7x64 机器上有和没有主题时都很好用,但是在同样运行 Win7x64 的测试虚拟机中,禁用主题时看起来很糟糕。我怀疑这取决于哪些其他 shell 扩展已将内容添加到菜单中,也许如果第 3 方扩展碰巧使用所有者绘制图标,它会将 shell 翻转到不同的代码路径,这使得新的 hbmpItem 方法也可以通过如果未设置主题,默认情况下它不起作用时发生意外。不过,这只是一个猜测。这种不一致似乎是 Windows 中的一个错误,鉴于它从 Vista 到 Windows 8.1 一直存在,因此不太可能修复它你只需要避免在禁用主题时使用它,即使在 Windows 7 或 8 上也是如此。)

无论主题是否启用,这在 Windows XP 上都无法正常工作。

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

  • 图标太靠右了,与菜单上的大多数其他图标位于不同的位置,并且由于菜单行高较小,因此看起来不错且与主题菜单中的其他图标一致的 16x16 图标将太大在非主题菜单中。

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

  • 因此,如果您关心这些情况,那么您必须回退并且在旧系统或非主题系统上根本不提供图标,或者提供替代图标。(将其设为 13x13,并在运行时自行将其与系统菜单颜色混合。)您还需要使用 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。
  • 在 Channels 选项卡上,添加一个新层(它应该自动命名为“Alpha”)。
  • 用黑色填充整个 Alpha 通道。
  • 返回“图层”选项卡并ctrl单击主图层(它应该是目前唯一的图层)以选择其透明度蒙版。
  • 返回到 Channels 选项卡并单击 Alpha 通道。您在上一步中所做的选择应该仍然处于活动状态;用白色填充该选择。
  • (此时,您有一个具有显式 Alpha 通道的非预乘 Alpha 图像。)
  • 现在回到图层选项卡和主图层后面的新图层。用黑色填充它。
  • 最后,将文件另存为 32-bpp BMP。(确保在 Photoshop 的另存为对话框中勾选了 Alpha 通道复选框。)

alpha 通道(在添加背景层之前制作)和黑色背景层的组合导致预乘 alpha,预乘黑色。

于 2013-10-19T02:07:12.867 回答
1

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

我认为您应该坚持使用 GDI+ 加载图像,因为这确实可以让您获得 alpha 位 - 您只需要一种手动方法将这些位放入 aHBITMAP而不会丢失它们。

我对 .NET 的了解还不够,无法明确声明它不支持这一点,但我无法通过快速搜索找到简单的解决方案。所以我认为你最好的选择是用来Bitmap.LockBits访问原始数据,然后CreateDIBSection()通过pinvoke使用并将这些位自己复制到 DIB 部分。

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

于 2013-10-11T20:02:48.940 回答