使用分层窗口及透明png图片实现一个异形窗口

系统 1575 0

搞Windows程序的人尽皆知分层窗口能够实现很多不错的效果,之前看过一些异形窗口的实现,所以就手痒也想自己搞一个玩一玩。自己动手实现过程才发现还是有不少问题的。

基本思路是:
1.将窗口扩展属性设置为分层属性WS_EX_LAYERED。
2.选一张透明的png图片,并将其加载进来。
3.创建与窗口兼容的内存设备上下文,以及兼容位图,将兼容位图选入兼容设备上下文。
4.将png图片绘制到内存设备上下文中。
5.设置BLENDFUNCTION结构,调用UpdateLayeredWindow。


第一步设置窗口的分层属性比较简单:

      windowStyle =
      
         GetWindowLong(hWnd, GWL_EXSTYLE);
windowStyle 
      
      = windowStyle |
      
         WS_EX_LAYERED;
SetWindowLong(hWnd, GWL_EXSTYLE, windowStyle);
      
    


第二步是将png图片加载到程序中,ATL的CImage和GDI+的Image这两个类比较常用。使用CImage直接通过Load方法加载绝对路径图片或者内存中的图片,我这里就是使用CImage类实现。代码:

      
        //
      
      
        CImage类方式加载图片
      
      
        CImage img;
img.Load(TEXT(
      
      
        "绝对路径
      
      
        png图片
      
      
        "
      
      )); 
      
        //
      
      
        将图片与类关联起来
      
    

然后是使用Image类方式:

      
        //
      
      
        Image类方式加载图片
      
      
Image* pImage = Image::FromFile(_T(
      
        "
      
      
        绝对路径png图片
      
      
        "
      
      ));
    


第三步比较繁琐点。加载了图片后就需要创建一个位图句柄HBITMAP,创建位图句柄有两种方式:CreateCompatibleBitmap和CreateDIBSection这两个函数。先介绍下这两个函数。

      HBITMAP CreateCompatibleBitmap( HDC hdc, 
      
        //
      
      
         handle to DC
      
      
        int
      
       nWidth, 
      
        //
      
      
         width of bitmap, in pixels
      
      
        int
      
       nHeight 
      
        //
      
      
         height of bitmap, in pixels);
      
    
      HBITMAP CreateDIBSection( HDC hdc, 
      
        //
      
      
         handle to DC
      
      
    CONST BITMAPINFO *pbmi, 
      
        //
      
      
         bitmap data
      
      
    UINT iUsage, 
      
        //
      
      
         data type indicator
      
      
    VOID **ppvBits, 
      
        //
      
      
         bit values
      
      
    HANDLE hSection, 
      
        //
      
      
         handle to file mapping object
      
      
    DWORD dwOffset 
      
        //
      
      
         offset to bitmap bit values
      
      
);
    

首先看看CreateCompatibleBitmap函数的代码:

      
        //
      
      
        CreateCompatibleBitmap函数创建
      
      
        hdc = GetDC(hWnd); 
      
        //
      
      
        hWnd为需要分层窗口的句柄
      
      
        hdcMem = CreateCompatibleDC(hdc); 
      
        //
      
      
        创建与hdc相兼容的内存句柄
      
      
        hBitmap = CreateCompatibleBitmap(hdc, sz.cx, sz.cy); 
      
        //
      
      
        创建与hdc相兼容的位图句柄
      
      
        SelectObject(hdcMem,(HGDIOBJ)hBitmap); 
      
        //
      
      
        将位图选入内存句柄作为画板   
      
    

然后是使用CreateDIBSection函数的代码:

      
        //
      
      
        CreateDIBSection函数创建
      
      
        hdc =
      
         GetDC(hWnd);
        hdcMem 
      
      =
      
         CreateCompatibleDC(hdc);

        BITMAPINFO bitmapinfo;
        bitmapinfo.bmiHeader.biSize 
      
      = 
      
        sizeof
      
      
        (BITMAPINFOHEADER);
        bitmapinfo.bmiHeader.biBitCount 
      
      = 
      
        32
      
      
        ;
        bitmapinfo.bmiHeader.biHeight 
      
      =
      
         sz.cy;
        bitmapinfo.bmiHeader.biWidth 
      
      =
      
         sz.cx;
        bitmapinfo.bmiHeader.biPlanes 
      
      = 
      
        1
      
      
        ;
        bitmapinfo.bmiHeader.biCompression
      
      =
      
        BI_RGB;
        bitmapinfo.bmiHeader.biXPelsPerMeter
      
      =
      
        0
      
      
        ;
        bitmapinfo.bmiHeader.biYPelsPerMeter
      
      =
      
        0
      
      
        ;
        bitmapinfo.bmiHeader.biClrUsed
      
      =
      
        0
      
      
        ;
        bitmapinfo.bmiHeader.biClrImportant
      
      =
      
        0
      
      
        ;
        bitmapinfo.bmiHeader.biSizeImage 
      
      = bitmapinfo.bmiHeader.biWidth * bitmapinfo.bmiHeader.biHeight * bitmapinfo.bmiHeader.biBitCount / 
      
        8
      
      
        ;

        hBitmap 
      
      = ::CreateDIBSection(hdcMem,&bitmapinfo, 
      
        0
      
      ,NULL, 
      
        0
      
      , 
      
        0
      
      
        );
        SelectObject(hdcMem,(HGDIOBJ)hBitmap);    
      
    


第四步,将png图片绘制到内存设备上下文中。这一步根据前面加载图片的两种方式对应不同的绘制函数。

CImage类方式代码:

      
        //
      
      
        CImage类方式代码

      
      
        //
      
      
        将img关联的png图片绘制到内存句柄中
      
      
img.Draw(hdcMem, 
      
        0
      
      , 
      
        0
      
      , sz.cx, sz.cy, 
      
        0
      
      , 
      
        0
      
      
        , sz.cx, sz.cy);

      
      
        //
      
      
        hdcMem为内存兼容句柄,之后四个参数表明了在内存兼容句柄中绘制的位置,最后四个参数表示了img关联的png图片的位置
      
    

然后是Image类方式处理代码:

      
        //
      
      
        Image类方式代码
      
      
        Graphics g(hMemDC);
g.DrawImage( pImage, 
      
      
        0
      
      , 
      
        0
      
      );
    


第五步:设置BLENDFUNCTION结构,调用UpdateLayeredWindow。

设置BLENDFUNCTION结构代码如下:

      
        BLENDFUNCTION  bf;
bf.AlphaFormat 
      
      = AC_SRC_ALPHA;
      
        //
      
      
        源位图具有Alpha通道
      
      
bf.BlendFlags = 
      
        0
      
      ;
      
        //
      
      
        必须为0
      
      
bf.BlendOp = AC_SRC_OVER;
      
        //

      
      bf.SourceConstantAlpha = 
      
        255
      
      ;
      
        //
      
      
        设置透明度
      
    

然后就是调用UpdateLayeredWindow函数了

      UpdateLayeredWindow(hWnd, hdc, NULL, &sz, hdcMem, &pt, NULL, &bf, ULW_ALPHA);
    

看看MSDN对UpdateLayeredWindow函数的介绍:

      
        BOOL UpdateLayeredWindow(
    HWND hwnd,
      
      
        //
      
      
        需要分层的窗口句柄
      
      
    HDC hdcDst, 
      
        //
      
      
        需要分层窗口设备句柄
      
      
    POINT *pptDst,
      
        //
      
      
        窗口位置不发生变化可以设置为NULL
      
      
    SIZE *psize,
      
        //
      
      
        窗口大小不发生变化可以设置为NULL
      
      
    HDC hdcSrc,
      
        //
      
      
        绘制源的设备句柄
      
      
    POINT *pptSrc,
      
        //

      
          COLORREF crKey,
      
        //
      
      
        指定一个透明色,使用ULW_COLORKEY标志时有效,也就是说crKey为白色时候,那么位图上所有白色的地方均为透明,其他地方不透明
      
      
    BLENDFUNCTION *pblend,
      
        //
      
      
        之前介绍过了
      
      
    DWORD dwFlags
      
        //
      
      
        ULW_ALPHA使用Alpha通道,ULW_COLORKEY使用crKey作为透明色,ULW_OPAQUE不透明
      
      
);
    

这样异形窗口就算完成了。


整理以下代码如下:

      LONG windowStyle =
      
         GetWindowLong(hWnd, GWL_EXSTYLE);
windowStyle 
      
      = windowStyle |
      
         WS_EX_LAYERED;
SetWindowLong(hWnd, GWL_EXSTYLE, windowStyle);

CImage img;
img.Load(TEXT(
      
      
        "
      
      
        png图片
      
      
        "
      
      
        ));

SIZE sz;
      
      
        //
      
      
        图片大小
      
      
sz.cx =
      
         img.GetWidth();
sz.cy 
      
      =
      
         img.GetHeight();

SetWindowPos(hWnd, NULL, 
      
      
        0
      
      , 
      
        0
      
      , sz.cx, sz.cy, SWP_NOREDRAW);
      
        //
      
      
        将窗口大小设置为图片大小使之相互合适
      
      
        
HDC hdc 
      
      = GetDC(hWnd);
      
        //
      
      
        获取窗口设备句柄
      
      
HDC hdcMem = CreateCompatibleDC(hdc);
      
        //
      
      
        创建一个与hdc相兼容的内存设备句柄
      
      
HBITMAP    hBitmap =
      
         CreateCompatibleBitmap(hdc, sz.cx, sz.cy);
SelectObject(hdcMem,(HGDIOBJ)hBitmap);
img.Draw(hdcMem, 
      
      
        0
      
      , 
      
        0
      
      , sz.cx, sz.cy, 
      
        0
      
      , 
      
        0
      
      
        , sz.cx, sz.cy);

POINT pt;
pt.x 
      
      = 
      
        0
      
      
        ;
pt.y 
      
      = 
      
        0
      
      
        ;

BLENDFUNCTION  bf;
bf.AlphaFormat 
      
      =
      
         AC_SRC_ALPHA;
bf.BlendFlags 
      
      = 
      
        0
      
      
        ;
bf.BlendOp 
      
      =
      
         AC_SRC_OVER;
bf.SourceConstantAlpha 
      
      = 
      
        255
      
      
        ;

UpdateLayeredWindow(hWnd, hdc, NULL, 
      
      &sz, hdcMem, &pt, NULL, &
      
        bf, ULW_ALPHA);
ReleaseDC(hWnd, hdc);
      
    


说说遇到的问题,最开始就碰到了一个问题发现png图片没有办法产生异形的效果,然后才发现原来我使用的png图片不是透明png图片。

当png透明图片问题解决后,发现窗口出现后,本来那个应该是透明的地方却是白色不透明的了。这个问题网上也是有介绍的,我在 这里 得到了答案。下面我把原因重新贴一下:

PNG图片的透明背景总是一片白色,后来才发现这其实是微软GDI+的设计问题,PNG图片是ARGB,使用GDI+载入图片的时候,GDI+会默认已经进行了预剩运算(PARGB),即每象素的实际值是已经和ALPHA值按比例相乘的结果,实际上它根本就没有做预乘,在使用透明图片的象素ALPHA通道的时候,CImage内部正是调用的AlphaBlend,没有预乘的图当作预乘的图片处理的结果就是这相当于一张和纯白背景进行了预剩,所以图象总是出现白色背景。

下面给出解决这个问题的代码,代码来源 这里 ,处理还是比较简单的:

      
        if
      
       (pImage->GetBPP() == 
      
        32
      
      ) 
      
        //
      
      
        确认该图像包含Alpha通道
      
      
           {
       
      
      
        for
      
       (inti=
      
        0
      
      ; i<pImage->GetWidth();i++
      
        )
       {
            
      
      
        for
      
      (
      
        int
      
       j=
      
        0
      
      ; j<pImage->GetHeight(); j++
      
        )
            {
                
      
      
        byte
      
      *pByte = (
      
        byte
      
      *)pImage->
      
        GetPixelAddress(i, j);
                pByte[
      
      
        0
      
      ]= pByte[
      
        0
      
      ] * pByte[
      
        3
      
      ]/ 
      
        255
      
      
        ;
                pByte[
      
      
        1
      
      ]= pByte[
      
        1
      
      ] * pByte[
      
        3
      
      ]/ 
      
        255
      
      
        ;
                pByte[
      
      
        2
      
      ]= pByte[
      
        2
      
      ] * pByte[
      
        3
      
      ]/ 
      
        255
      
      
        ;
            }
       }
   }
      
    

这样异形窗口也算是完成了,但是这样生成的exe文件需要依赖外部的png图片,所以我应该把那张png图片放到资源文件中,然后直接生成的exe就包含了那张png图片。这就涉及到了如何从资源中加载图片的问题。

使用分层窗口及透明png图片实现一个异形窗口


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论