带区域和抗锯齿边缘的透明按钮






4.83/5 (44投票s)
创建透明、混合位图按钮的代码。
引言
这是一个如何创建和使用蒙版半透明位图按钮的示例,这些按钮可以叠加在任何背景上,包括其他位图。按钮会自动与背景进行 alpha 混合(抗锯齿),以创建平滑的边缘,并提供区域剪裁以创建各种椭圆和其他形状。
没有创建复制所有所有者绘制按钮功能的新类,而是将绘制透明度和创建区域的代码添加到了 Niek Albers 编写并由 Frederick Ackers 修改的 CHoverButtonEx 类中。如果您已经在使用 CHoverButtonEx
类,则只需用此版本替换它,而不会影响现有功能,尽管我还在位图中添加了第四个图像用于禁用视图,因此您需要在现有位图中包含此图像,或者您需要将引用改回 3。
演示项目使用一个基于对话框的区域化窗口,该窗口将一个圆形、图案化的位图作为背景。这种位图背景显示了这些按钮如何获得非常时尚的外观和感觉。通过按下窗口底部区域的四个刷新按钮中的任何一个,背景就会更改为不同的位图(共有六个)。
感谢 Paul Nettle 的 winDIB 例程。
关于抗锯齿和 Alpha 混合
在将图像叠加在图像上时,最棘手的部分之一是这些图像的相交处的粗糙边缘或像素化,当它们不是直线边缘时。解决这些粗糙边缘的方法是将边缘混合到一种称为抗锯齿的技术中。在此演示中,抗锯齿是通过 alpha 混合技术完成的,其中前景位图像素的透明度决定了显示器上任何特定位置的颜色强度。
混合源像素和背景像素的公式为:
displayColor = sourceColor × sourceAlpha / 255 + backgroundColor × (255 – sourceAlpha) / 255
其中 alpha 值范围是从 0(透明)到 255(不透明)。因此,按钮位图的任何 alpha 值为 0 的部分都将不可见,而值为大于零的区域将以不同强度与背景混合,直到 255,此时按钮位图将完全不透明。
// alphaBlend routine static inline unsigned int alphaBlend(const unsigned int bg, const unsigned int src) { unsigned int a = src >> 24; // sourceColor alpha // If source pixel is transparent, just return the background if (0 == a) return bg; // alpha-blend the src and bg colors unsigned int rb = (((src & 0x00ff00ff) * a) + ((bg & 0x00ff00ff) * (0xff - a))) & 0xff00ff00; unsigned int g = (((src & 0x0000ff00) * a) + ((bg & 0x0000ff00) * (0xff - a))) & 0x00ff0000; return (src & 0xff000000) | ((rb | g) >> 8); }
创建位图
如前所述,为了将按钮位图与背景混合,按钮位图必须为每个像素分配 alpha 值。这意味着这些位图必须是每像素 32 位(32bpp)的图像,其中字节顺序为 Alpha、Red、Green、Blue(ARGB)。
创建这种类型的图像的一种方法是使用 Adobe Photoshop。创建一个具有 CMYK 颜色模式的新图像,确保背景颜色设置为黑色。Photoshop 可以自动为您针对黑色背景进行边缘抗锯齿处理。将文件保存为 .RAW 类型,它只保存图像的实际字节而不保存任何头信息。不幸的是,.RAW 图像的顺序将是 ABGR,因此在混合之前需要进行一些字节重排序,但这并不难。
将位图作为新的资源类型“RAW”导入到您的项目中。现在可以加载位图作为资源供按钮使用。
// This routine loads raw bitmap data from a resource. // The width and height must be defined as there is no bitmap header // // Note that the resource type is "RAW" here, but can be changed to // any name, or passed as a parameter if desired BOOL CHoverButtonEx::LoadRaw(UINT rawid, long nWidth, long nHeight) { //If Bitmaps are being loaded and the BS_OWNERDRAW is not set // then reset style to OwnerDraw. UINT style = GetButtonStyle(); if (!(style & BS_OWNERDRAW)) { style |= BS_OWNERDRAW; SetButtonStyle(style); } m_pRaw = NULL; CString resName; resName.Format("#%d", rawid); HGLOBAL hRaw = LoadResource(AfxGetResourceHandle(), FindResource(AfxGetResourceHandle(), resName, "RAW")); if (!hRaw) return FALSE; m_pRaw = (unsigned int*)LockResource(hRaw); if (!m_pRaw) return FALSE; m_nRawWidth = nWidth; m_nRawHeight = nHeight; // The bitmap should contain four images: // normal, selected, hover, and disabled. // The images must be the same size within // the bitmap, but can be laid out // horizontally or vertically. if (!m_bHorizontal) { m_ButtonSize.cy=nHeight/4; m_ButtonSize.cx=nWidth; } else { m_ButtonSize.cy=nHeight; m_ButtonSize.cx=nWidth/4; } SetWindowPos( NULL, 0,0, m_ButtonSize.cx, m_ButtonSize.cy,SWP_NOMOVE | SWP_NOOWNERZORDER ); return TRUE; }
使用代码
添加透明位图按钮非常容易实现。
- 在您的对话框或视图上创建任意数量的所有者绘制按钮。
- 在声明按钮的对话框或视图头文件中包含 HoverButton 头文件。
#include "HoverButton.h" . .
- 将按钮类型更改为
CHoverButtonEx
。CHoverButtonEx m_Btn1; CHoverButtonEx m_Btn2; CHoverButtonEx m_Btn3;
- 为按钮创建区域并分配参数。
// Create round buttons HRGN r = CreateEllipticRgn(0, 0, 48, 48); m_Btn1.SetHorizontal(TRUE); m_Btn1.SetRegion(r); m_Btn1.LoadRaw(IDR_RED, 192, 48); m_Btn1.SetToolTipText("Press Me!"); m_Btn2.SetHorizontal(TRUE); m_Btn2.SetRegion(r); m_Btn2.LoadRaw(IDR_PURPLE, 192, 48); m_Btn2.SetToolTipText("Press Me!"); m_Btn3.SetHorizontal(TRUE); m_Btn3.SetRegion(r); m_Btn3.LoadRaw(IDR_PURPLE, 192, 48); m_Btn3.SetToolTipText("Press Me!"); DeleteObject(r);
- 如果背景发生变化,只需调用
RefreshBkgrnd
。m_Btn1.RefreshBkgrnd(); m_Btn2.RefreshBkgrnd(); m_Btn3.RefreshBkgrnd();
就是这样!
关注点
这里一个有趣的例程是如何确定背景的外观。我决定在按钮首次显示之前读取背景,并保存该图像直到按钮刷新。
// If this is the first time drawing the button, or the background // has been refreshed, we need to "read" the background and save // it for transparency mixing (aka alpha blending). if (m_pRaw && m_nRawWidth && m_nRawHeight && !m_bLoadBkgrnd) { unsigned int bkPix; COLORREF c; int bkWidth; int bkHeight; // Get the size of one image if (!m_bHorizontal) { bkWidth = m_nRawWidth; bkHeight = m_nRawHeight/4; } else { bkWidth = m_nRawWidth/4; bkHeight = m_nRawHeight; } if (m_pBkGrnd) delete m_pBkGrnd; // Create array to hold background pixels m_pBkGrnd = new unsigned int[bkWidth*bkHeight]; if (!m_pBkGrnd) return; unsigned int* pBkGrnd = m_pBkGrnd; for (int iY = 0; iY < bkHeight; iY++) { for (int iX = 0; iX < bkWidth; iX++, pBkGrnd++) { // Get the background pixel c = mydc->GetPixel(iX, iY); bkPix = (unsigned int)c; bkPix |= 0xff000000; // Set pixel opaque // Set correct order of RGB bkPix = (bkPix&0xff00ff00) | ((bkPix>>16)&0xff) | ((bkPix<<16)&0xff0000); // Save pixel in array *pBkGrnd = bkPix; } } m_bLoadBkgrnd = TRUE; }
这种方法提供了背景的准确图片,特别是如果背景是从原始图像拉伸或平铺的。确定背景的另一种方法可能是将背景位图传递给按钮类,并计算偏移量,或者如果背景不是位图,则简单地传递颜色引用。在选定的情况下,这些其他方法中的任何一种都可能更有效。