进阶《窗体透明示例三》如何用GDI实现透明、背景动画或换肤的效果
为简化只做了窗体的顶部比较复杂的一块,白云飘飘、淡入淡出效果截图:执行文件:
Gs-AlphaWIn.zip
(178.42 KB)
虽然说,采用wpf可以很轻松的实现一些特效,可一来需要.net,二是c#,三是vista以上...这些限制让汇编爱好者“屡试不爽”。
以下是前段时间写的小东西的体会,小结一些,可能表述不甚好或笔误,有不明白或有更好的想法可以分享交流一下。
1. 如何实现窗体透明,控件不透明
为了更加轻松地做到这一点,DIB位图采用RGBA的32位带alpha通道的真彩位图(也由RGBA的png转成dib32),如果是自绘控件的话,可以这块全不透明即可alpha=0xff。
"Try the above procedure. Create your bitmap and make sure the part where the webbrowser control should appear is not transparent;"
参考: https://bbs.bccn.net/viewthread.php?tid=359762&page=1#pid2332590
关键点:
(1).CreateWindowEx中使用WS_EX_LAYERED 图层窗体类型
(2).窗体更新使用UpdateLayeredWindow
程序代码:
BOOL WINAPI UpdateLayeredWindow( _In_ HWND hwnd, _In_opt_ HDC hdcDst, _In_opt_ POINT *pptDst, _In_opt_ SIZE *psize, _In_opt_ HDC hdcSrc, _In_opt_ POINT *pptSrc, _In_ COLORREF crKey, _In_opt_ BLENDFUNCTION *pblend, _In_ DWORD dwFlags );
最后一个参数dwFlags选择ULW_ALPHA,而参数pblend比较关键,它是一个已初始化赋值了的BLENDFUNCTION结构的指针
程序代码:
typedef struct _BLENDFUNCTION { BYTE BlendOp; BYTE BlendFlags; BYTE SourceConstantAlpha; BYTE AlphaFormat; }BLENDFUNCTION, *PBLENDFUNCTION, *LPBLENDFUNCTION;
这里,我们初始化为:
pblend BLENDFUNCTION AC_SRC_OVER,0,255,AC_SRC_ALPHA
也即,使用DIB32中的alpha进行混合运算(实质是加权平均),公式如下(见msdn):
If the source bitmap has per-pixel alpha, the constant alpha is not used (0xff) as shown in the following table.
Dst.Red = Src.Red + (1 - Src.Alpha) * Dst.Red
Dst.Green = Src.Green + (1 - Src.Alpha) * Dst.Green
Dst.Blue = Src.Blue + (1 - Src.Alpha) * Dst.Blue
If the destination bitmap has an alpha channel, then:
Dest.alpha = Src.Alpha + (1 - SrcAlpha) * Dst.Alpha
*****还有一个决定成败的是,这里的src和dst都是预先经过预乘alpha的数据,
比如Src上的一个点为(RGBA) 为db 0x24,0x56,0xf5,0x33
经过预乘alpha的数据为db 0x24*0x33/256,0x56*0x33/256,0xf5*0x33/256,0x33
asm片段:
程序代码:
xloop: mov eax,[esi] mov edx,eax rol eax,16 shr edx,24 ; A mul dl ; B * A rol eax,8 mul dl ; G * A rol eax,8 mul dl ; R * A mov al,dl ror eax,8 mov [esi],eax add esi,4 dec ecx jnz xloop
所以,你看到的素材透明的部分偏黑,是已经经过预乘alpha处理了的,当然你也可以在载入bmp后预乘一次即可。
(3).DIB32位图(RGBA)载入CreateDIBSection
程序代码:
HBITMAP CreateDIBSection( HDC hdc, // handle to device context CONST BITMAPINFO *pbmi, // pointer to structure containing bitmap size, // format, and color data UINT iUsage, // color data type indicator: RGB values or // palette indexes VOID *ppvBits, // pointer to variable to receive a pointer to // the bitmap's bit values HANDLE hSection, // optional handle to a file mapping object DWORD dwOffset // offset to the bitmap bit values within the // file mapping object );第四个参数ppvBits,调用CreateDIBSection之后,ppvBits是一个未初始化的数据存储块的指针,
通过memcopy等方式,将DIB32的数据拷贝进去即可,(否则可能就出现网上说的显示一片黑)。
(4).对窗体的渲染都是针对ppvBits所指向的这块内存进行的,然后用UpdateLayeredWindow进行更新。
而UpdateLayeredWindow能为我们做什么呢?它可以做的是将ppvBits所指向的这块内存与桌面位图进行alpha混合,这样
就不需要我们来获得桌面位图,通常我们这样做的效率不高,也无法做到即时更新(拖动窗体或桌面背景不变化)。
2.如何实现背景动画?
对于这个问题,在没解决之前我考虑了一整天,网上也找不到相关的东西,或许很多人以此...你懂的,一直在纠结是不是应该获取桌面位图进行混合后,再与ppvBits所指向的这块前景内存
混合,这样就可以实现背景变动了。其实不然,UpdateLayeredWindow能为我们做的不多,如1(4)提到的。
在看到BLENDFUNCTION后顿悟了。
既然UpdateLayeredWindow能实现ppvBits所指向的这块内存与桌面位图进行alpha混合,为何我们不能在ppvBits所指向的这块内存之上进行alpha混合呢。
这是关键的,答案是可行的。图层的思想体现出来了,这个层次是自行设计的,比如云朵作为一个图层A,太阳作为另一个图层B,主景再作一个层C。
对层进行巧妙有序的安排,比如云朵可能要在太阳的上面顠,又能在主景的后面。这个在做混合(Dib32AlphaBlend)的时候,图层的混合次序可以为:
B=AlphaBlend(B,C) -> B=AlphaBlend(B,A) -> copy(ppvBits,B) ->UpdateLayeredWindow(ppvBits)
比如QQ聊天窗体可以如下排布:
Dib32AlphaBlend的混合算法见1(2)中对BLENDFUNCTION的说明部分。
对于Dib32AlphaBlend我也实现了一个效率不算高,但是有效的代码:
说明:将pBitDst这块内存的pDstRect位置处与pBitSrc这块内存的pSrcRect处的(dwWight*dwHight)矩阵块进行alpha混合。
有个细节要提醒一下,DIB32位图倒过来成像的,存储的第一行显示出来,也即在窗体的最底部一行,所以DibCoordinate来完成这个转换。
程序代码:
proc Dib32AlphaBlend uses esi edi, pBitDst,pDstRect,pBitSrc,pSrcRect,dwWight,dwHight local dwSrcWight:DWORD,dwDstWight:DWORD mov edx,[pSrcRect] mov eax,[edx+RECT.right] shl eax,2 mov [dwSrcWight],eax stdcall DibCoordinate,[pBitSrc],edx,[dwHight] mov esi,eax ;--------------------------------------- mov edx,[pDstRect] mov eax,[edx+RECT.right] shl eax,2 mov [dwDstWight],eax stdcall DibCoordinate,[pBitDst],edx,[dwHight] mov edi,eax ;--------------------------------------- .loopy: mov edx,[dwWight] push edi push esi .loopx: mov cl,255 sub cl,[esi+3] mov al,[edi] mul cl ;ax=al*cl add ah,[esi] mov [edi],ah mov al,[edi+1] mul cl ;ax=al*cl add ah,[esi+1] mov [edi+1],ah mov al,[edi+2] mul cl ;ax=al*cl add ah,[esi+2] mov [edi+2],ah mov al,[edi+3] mul cl ;ax=al*cl add ah,[esi+3] mov [edi+3],ah add esi,4 add edi,4 sub edx,1 jnz .loopx pop esi pop edi add esi,[dwSrcWight] add edi,[dwDstWight] sub [dwHight],1 jnz .loopy ret endp3.如何换肤,这个更加容易,只需要更换一下A、B或C的图。