如何为 32 位位图生成单色位掩码
Posted
技术标签:
【中文标题】如何为 32 位位图生成单色位掩码【英文标题】:How to generate a monochrome bit mask for a 32bit bitmap 【发布时间】:2011-04-25 23:03:11 【问题描述】:在 Win32 下,通过执行以下操作从位图生成单色位掩码以用于透明度是一种常用技术:
SetBkColor(hdcSource, clrTransparency);
VERIFY(BitBlt(hdcMask, 0, 0, bm.bmWidth, bm.bmHeight, hdcSource, 0, 0, SRCCOPY));
这里假设 hdcSource 是一个内存 DC 保存源图像,而 hdcMask 是一个内存 DC 保存相同大小的单色位图(所以两者都是 32x32,但源是 4 位颜色,而目标是 1 位单色)。
但是,当源是 32 位颜色 + alpha 时,这对我来说似乎失败了。我没有在 hdcMask 中获得单色位图,而是得到一个全黑的蒙版。没有位设置为白色 (1)。而这适用于 4 位颜色源。
我的 search-foo 失败了,因为我似乎找不到任何关于这个特定问题的参考资料。
我已经确定这确实是我的代码中的问题:即,如果我使用 16 色(4 位)的源位图,它可以工作;如果我使用 32 位图像,它会生成全黑蒙版。
对于 32 位彩色图像,我应该使用其他方法吗?覆盖上述技术的正常行为的 Alpha 通道是否存在问题?
感谢您提供的任何帮助!
附录:我仍然找不到为我的 GDI+ 生成的源位图创建有效单色位图的技术。
我通过根本不生成单色位掩码在一定程度上缓解了我的特定问题,相反我使用的是 TransparentBlt(),这似乎是正确的(但我不知道他们在内部做什么任何允许他们正确掩盖图像的不同)。
拥有一个非常好的工作函数可能会很有用:
HBITMAP CreateTransparencyMask(HDC hdc, HBITMAP hSource, COLORREF crTransparency);
无论 hSource 的颜色深度如何,它总是会创建一个有效的透明蒙版。
【问题讨论】:
GDI 卡在 24bpp。 TransparentBlt() 有点不寻常,它被记录为支持 32bpp。或许是时候转向 GDI+了。 【参考方案1】:如果有 Alpha 通道,你就不能这样做。 COLORREF 将前 8 位用于多种目的,包括指定低 3 字节是否是当前调色板的颜色表索引或 RGB 三元组。因此,您不能在 clrTransparency 的高字节中指定除 0x00 之外的任何内容。
如果您有一个 alpha 位图,那么对于仍然“不知道”alpha 通道的 GDI,没有明智的方法来实际比较位图中的 24 位 BkColor 和 32 位像素。
我希望 GDI 将 32bpp 位图中的 alpha 通道视为“保留”,并且仅成功比较保留通道为零的像素。即你的面具颜色必须完全透明才能有成功的机会。 (并且,如果您制作了合法的预乘位图,这意味着 RGV 值也将为零,而是限制了您对蒙版颜色的选择:P)
【讨论】:
在这种特殊情况下,问题是我从 4 位彩色位图开始。然后,为了在禁用时以一种很好的方式显示它,我想对其进行灰度渲染,然后将其用作按钮面(像以前一样使用蒙版透明度技术)。但是我用来生成灰度的代码是基于 GDI+ 的,我怀疑它会创建 32 位彩色图像而不是 24 位彩色图像。 @Mordachai:根据您刚才的描述,您能否从原始的 4 位图像制作蒙版并将其与 GDI 创建的蒙版一起使用? 我喜欢这个想法。但我仍然对通用的“如何为 ANY 源 HBITMAP 创建单色位掩码”感兴趣。 我认为any HBITMAP 的唯一方法是编写自己的。普通 GDI 中的许多颜色映射技术都特定于基于调色板的图像,不适用于非调色板图像。【参考方案2】:可以:) 正如上面 'Chris Becke' 所指出的,GDI 只能在保留的 Alpha 通道为零时进行比较。从 BITMAP::GetHBITMAP() 获得的 HBITMAP 返回一个 HBITMAP,其中 Alpha 通道全部设置为 0xFF。这必须为 0x00 才能使 SetBkColor() 比较起作用。因此,Soln: 循环遍历每个像素并将 Alpha 分量设置为零。
Bitmap img(L"X.bmp");
HBITMAP hBM;
img.GetHBITMAP(Color::White, &hBM);
BITMAP bm;
GetObject(g_hbmBall, sizeof(BITMAP), &bm);
for(UINT i = 0, n = -1; i < bm.bmHeight; i++)
for(UINT j = 0; j < bm.bmWidth; j++)
n += 4; // Once per Pixel of 4 Bytes
((LPBYTE)bm.bmBits)[n] = 0;
// Now SetBkColor and BitBlt will work as expected
【讨论】:
感谢您的想法。我最终决定根本不使用这种技术,所以这个编码分支目前已经失效。我将来可能会回到它,然后会再次尝试这个想法。 ;)【参考方案3】:对我有用的方法是先将位图从 32 位转换为 24 位。
1. CreateCompatibleDC
2. CreateDIBSection with 24 as the biBitCount.
3. SelectObject
4. BitBlt from 32bit DC to 24 bit. This removes alpha.
5. BitBlt from 24 bit DC to the monochrome DC works as expected.
在我的机器上,这比 Ujjwal 的回答中的双循环执行得更快。
【讨论】:
只是为了让这个方法更清晰,a。 3) SelectObject 将使用 CreateDIBSection 创建的位图选择到在 1) b 中创建的新 DC。为了创建蒙版,您需要确保在 1) 中创建的新 24 位 DC 上调用 SetBkColor,我不小心在 32 位 DC 上调用了 SetBkColor,并且花了一些时间进行故障排除。【参考方案4】:另一种方法是自己扫描像素并根据源颜色(或源 Alpha 与阈值)生成单色位图。
请注意,如果您使用的是 GDI+,则根据操作,像素可能已经过抗锯齿处理,导致它们都不是与您的“透明”颜色精确匹配的。
【讨论】:
我将 GDI+ 生成的灰度图像保存为 32 位深度 PNG,然后在 Paint 中对像素进行采样。他们没事。但是,我确实仔细检查了 GDI+ 生成的 HBITMAP(通过 Bitmap::GetHBitmap()),它确实总是 32 位深度,尽管底层的 Bitmap 实例是 PixelFormat24bppRGB)。以上是关于如何为 32 位位图生成单色位掩码的主要内容,如果未能解决你的问题,请参考以下文章
求单色位图,16色位图,24色位图和256色位图之间的区别?