此片文章是之前写的, 刚刚新开了博客, 就发出来跟你们分享下。算法
这篇文章主要讲得是vc中各类分层、透明、不规则窗口的实现, 基本囊括GDI、GDI+能使用的全部方法。api
本文讲述了三种方法,其中第一种方法有两种不一样效果,第三种方法有两种不一样的实现方式。文中有方法使用了GDi+,关于GDI+的使函数
用请自行查询资料,本文不进行细述。spa
方法一:窗体总体透明,支持子控件透明,支持OnPaint重绘。设计
这个方法比较简单,使用win32 Api 中SetLayeredWindowAttributes指针
函数便可,关于该函数可查询MSDN,用这种方法有两种效果:code
效果1:窗体总体透明,子控件也透明,能够实现半透明效果orm
//第一步要修改窗体属性,WS_EX_LAYERED支持透明blog
LONG lWindowStyle = ::GetWindowLong(hwnd, GWL_EXSTYLE) |图片
WS_EX_LAYERED;
//设置Alpha不透明度
BYTE byteAlpha = 150;
//注意最后一个参数为LWA_ALPHA,第二个参数颜色掩码(透明
//色无用)
SetLayeredWindowAttributes(m_hwnd, 0/*any*/, byteAlpha,
----------------------- Page 2-----------------------
LWA_ALPHA )
效果2:窗体总体透明,子控件不透明,实现不规则窗体,区域透明。
首先须要一张背景位图,须要透明的地方用单一颜色填充,而后将其
贴在背景上,代码以下
第一步跟效果一中同样需修改窗体属性
::SetWindowLong(hwnd, GWL_EXSTYLE, lWindowStyle);
//将红色设为透明色, 注意透明区域鼠标并不能穿透RGB(255, 0, 0)
//为透明色
//注意最后一个参数为LWA_COLORKEY,第三个参数透明度无用
::SetLayeredWindowAttributes(hwnd, RGB(255, 0, 0), 111/*any*/,
LWA_COLORKEY);
须要注意的是效果1和效果2能够结合起来使用,最后一个参数改为
LWA_COLORKEY | LWA_ALPHA便可。使用
SetLayeredWindowAttributes函数实现不规则形状简单易行,可是一般
会有锯齿很难处理。
方法二:根据位图进行区域裁剪 ,关键函数CombineRgn和SetWindowRgn。
该方法跟方法一同样,须要将背景位图须要透明的
地方填充为单一颜色,该方法的原理是遍历位图中的每一个像素,将需
要透明的像素过滤,将其余不须要透明的像素所在区域用
CombineRgn函数链接起来造成一个区域,而后用SetWindowRgn将贴
好背景图的窗体放进这个区域。此方法好处是能够实现镂空,即鼠标
穿透透明区域。缺点是遍历每一个像素对于大的位图算法时间复杂度高,
效率很低。代码以下:
void CMeterHeadDlg::SetupRegion(CDC & pDC, HBITMAP cBitmap, COLORREF TransColor) { CDC memDC; HBITMAP pOldMemBmp=NULL; BITMAP bit; CRect rect; GetWindowRect(rect); CRgn wndRgn; //建立于传入dc兼容的临时dc memDC.CreateCompatibleDC(pDC); //取得位图参数,要用其长和宽¨ª ::GetObjectA(m_hBkBitmap, sizeof(bit), &bit); //将位图选入临时dc pOldMemBmp= memDC.SelectBitmap(m_hBkBitmap); //建立总的窗体区域 wndRgn.CreateRectRgn(0,0,rect.Width(),rect.Height()); for(int y=0;y<rect.Height()+1;y++) { CRgn rgnTemp; //保存临时区域 int iX = 0; do { //等于透明色跳过找到下一个非透明色 if (memDC.GetPixel(iX, y) == TransColor) { rgnTemp.CreateRectRgn(iX,y,iX+1,y+1); //合并region,注意ComebineRgn最后一个参数为“异或” wndRgn.CombineRgn(wndRgn, rgnTemp, RGN_XOR); //删除临时region rgnTemp.DeleteObject(); } iX++; }while(iX <rect.Width()+1); iX = 0; } if(pOldMemBmp) memDC.SelectBitmap(pOldMemBmp); SetWindowRgn(wndRgn,TRUE); SetForegroundWindow(m_hWnd); DeleteDC(memDC); }
方法三:使用透明png贴图,并实现透明区域的透明。
此方法的优势是能够实现不规则形状贴图,鼠标能穿透透明区,而且边缘无锯齿。
该方法根据实现方式可分为两种方法
一、使用CImage(ATL和MFC中都有该类,直接用win32 api没有CImage,会麻烦点可能要用CreateFIle函数加载)绘制。
为何咱们正常的的使用CImage加载png透明区老是有白色背景呢?查了不少资料才
发现这实际上是微软GDI+的设计问题,PNG 图片是ARGB,使用GDI+
载入图片的时候,GDI+会默认已经进行了预剩运算(PARGB),即
每象素的实际值是已经和ALPHA值按比例相乘的结果,实际上它根
本就没有作预乘, 在使用透明图片的象素ALPHA通道的时候,
CImage 内部正是调用的AlphaBlend,没有预乘的图看成预乘的图片
处理的结果就是这至关于一张和纯白背景进行了预剩, 因此图象总
是出现白色背景。因此咱们只须要对症下药,载入图片前与处理下即
可:
if (Image.GetBPP() == 32) //确认32位包含alpha通道 for (i = 0; i < Image.GetWidth(); i++) { for (j = 0; j < Image.GetHeight(); j++) { byte *pByte =(byte*)Image.GetPixelAddress(i, j); pByte[0] = pByte[0] * pByte[3] / 255; pByte[1] = pByte[1] * pByte[3] / 255; pByte[2] = pByte[2] * pByte[3] / 255; } } }
最后调用CImage中Draw方法就可。
二、使用GDI+贴图,利用UpdateLayeredWindow
函数实现png透明区域透明。该函数请查询MSDN。这种方法不支持子
控件透明, 不支持OnPaint重绘代码以下:
首先在OnInitDialog中修改窗体属性
ModifyStyleEx(0, WS_EX_LAYERED | WS_OVERLAPPED);
下面为贴图函数,注意因为不支持OnPaint,因此需重绘是手动调用
贴图函数,贴图中使用到GDI+
//在OnInitDialog中初始化m_pBkImage ,m_pBkImage为
//Gdiplus::Image指针,Image::FromFile是从外面文件夹中导入png文
//件,若果是从本地资源文件中导入,需使用其余方法。
m_pBkImage = Image::FromFile(g_strResPath+_T("main_.png"));
下面为贴图函数
// 初始化时该函数放在OnInitDialog中调用,后面须要刷新时,手动 //调用,该方法贴的背景图不能响应WM_PAINT消息,也不可以在 //OnPaint函数中调用该绘图方法。 void CMainPanel::DrawAlphaPng() { CRect rcClient; GetClientRect(&rcClient); CClientDC dc(m_hWnd); CDC memDc; memDc.CreateCompatibleDC(dc.m_hDC); CBitmap bmp; bmp.CreateCompatibleBitmap(dc.m_hDC, rcClient.Width(), rcClient.Height()); memDc.SelectBitmap(bmp); //用GDI+显示图片 Graphics graph(memDc.m_hDC); graph.DrawImage(m_pBkImage, 0,0 ,rcClient.Width(), rcClient.Height()); BLENDFUNCTION _Blend; _Blend.BlendOp = 0; _Blend.BlendFlags = 0; _Blend.AlphaFormat = 1; _Blend.SourceConstantAlpha = 255; SIZE sz = {rcClient.Width(), rcClient.Height()}; //::UpdateLayeredWindow(m_hWnd, hDC, &ptWinPos,&sizeWindow, //hdcMemory, &ptSrc, 0, &stBlend, ULW_ALPHA); UpdateLayeredWindow(m_hWnd, dc, &CPoint(0, 0), &sz, memDc, &CPoint(0, 0), 0, &_Blend, ULW_ALPHA); bmp.DeleteObject(); graph.ReleaseHDC(memDc.m_hDC); ReleaseDC(dc.m_hDC); }
须要注意的是使用方法三中第二种方法虽然不会出现锯齿,可是
会致使界面上的子空间所有透明,这样咱们在界面上添加的控件都没
用。我试过在界面上用Create方法建立及对控件重绘,可是没用。解
决这个问题的方法是结合方法一中的效果二。须要两个窗口A (背景
窗口),B(用于放置控件)。用方法三第二种方式实现窗口A,使用
方法一种第二种效果建立B,MFC中能够在OnCtlColor函数中将背景
颜色设为单一颜色,ATL中则没有OnCtlColor,最简单的作法是在
OnEraseBKGND消息函数中返回TRUE(背景会变成白色),而后将背
景颜色设为透明色(这时B窗体会全透明,可是其上控件不会透明),
将A上所须要的控件放在B上相应位置,B窗口覆盖在A上面与其重合
(因为B透明因此B上控件看着像放在A上)。在移动B窗口时同时移
动A窗口。这样就能达到咱们想要的效果。
除了这三种方法以外,使用GDI中TransparentBlt函数也能够实现
透明,该函数能够将一张有背景的贴图消除背景贴在窗体上。关于该
函数使用就再也不介绍了,能够查询MSDN
以上所述三种方法本人都尝试过,都是可行的。若有疑问请向我
咨询。