实现不规则窗口界面

以前在研究不规则半透明窗口的时候,用的是gdi的TextOut/DrawText绘制字体,但绘制出的字体是透明的,
现在我完全放弃了GDI的TextOut()、BitBlt()的方式,而用SetDIBitsToDevice()代替,就是直接贴像素到DC里。

今天尝试用SetDIBitsToDevice()+UpdateLayeredWindow()实现不规则半透明窗口,结果非常顺利!
贴出来,共享给需要的朋友.

另外,本程序还附带了一个使用MMX汇编优化的32位alpha混合函数(57行,有详尽注释),需要的朋友可以直接copy了。

PS:关于字体的绘制
我建议大家用GetGlyphOutline()手动提取字形像素去绘制,因为TextOut这类绘制函数功能有限,只适合初级软件使用,而要想画出丰富多彩的界面,比如字体alpha渐变,就必须自己操作像素,所以GetGlyphOutline()才是专业级的,而且兼容性好,可以抽象出来提供给ddraw、d3d、opengl等使用

截图:

 

修改后的完整源码:(图像数据在代码里,但内容需要自己填充)

 

C/C++ code
    
    
#include < windows.h > #include < crtdbg.h > #define WND_WIDTH 100 #define WND_HEIGHT 100 #define WND_CLASSNAME "MainWnd" #define WND_TITLENAME "Test" struct SBmpInfo { BITMAPINFO m_BitmapInfo; RGBQUAD m_bmiColors[ 2 ]; // 为BITMAPINFO的m_bmiColors补充两个元素空间 }; struct SGDI { HWND m_hWnd; HDC m_hMainDC; HDC m_hMemoryDC; HBITMAP m_hMainSurface; HBITMAP m_hOldBitmap; int m_Width; int m_Height; UINT * m_pBackBuffer; SBmpInfo m_BmpInfo; }; SGDI g_GDI; UINT g_Bmp32[ 40 * 39 ]; // 这个图像数据需要自己填充. int g_bmpWidth = 40 ; int g_bmpHeight = 39 ; void AlphaBlend32(UINT * pDstBmp, int dst_width, UINT * pSrcBmp, int src_width, int blend_width, int blend_height) { const int nextLineOffset_src = (src_width - blend_width) * 4 ; // 混合完一行像素后,通过加上该值,便可直接定位到下行起始像素 const int nextLineOffset_dst = (dst_width - blend_width) * 4 ; __asm { mov edi, pDstBmp ; 目的像素 mov esi, pSrcBmp ; 源像素 xor ebx, ebx ; 已混合的高度 mov ecx, blend_width ; 要混合的宽度 BLEND_BEGIN: cmp dword ptr[esi], 0x00FFFFFF ; 如果alpha为0,则跳过混合部分 jna BLEND_END movd mm0, [edi] ; 把目的像素值移入mm0寄存器的低32位 movd mm1, [esi] ; 把源像素值移入mm1寄存器的低32位 ; Core Begin:result = b - (b - a) * a_alpha / 255 (a为源像素分量,b为目的像素分量) pxor mm2, mm2 ; ① 把MM2清0 punpcklbw mm0, mm2 ; 将mm0与mm2按字节交叉组合,存入mm0,mm0 = 0x00AA00BB00GG00RR punpcklbw mm1, mm2 ; 将mm1与mm2按字节交叉组合,存入mm1,mm1 = 0x00AA00BB00GG00RR movq mm3, mm1 ; ② mm3 = 0x00AA00BB00GG00RR punpckhwd mm3, mm3 ; 将高32位按16位交错排列,mm3 = 0x00AA00AA00BB00BB punpckhdq mm3, mm3 ; 将高32位按32位交错排列,mm3 = 0x00AA00AA00AA00AA movq mm4, mm0 ; ③ mm4 = 目的像素 = 0x00AA00BB00GG00RR movq mm5, mm1 ; mm5 = 源像素 = 0x00AA00BB00GG00RR psubusw mm4, mm1 ; ④ dst - src,按字饱和减,小于0为0 psubusw mm5, mm0 ; src - dst,按字饱和减,小于0为0 pmullw mm4, mm3 ; (dst - src) * alpha,若dst - src为0,则mm4为0 pmullw mm5, mm3 ; (src - dst) * alpha,若src - dst为0,则mm5为0 psrlw mm4, 8 ; 按字右移8位,即除以256 psrlw mm5, 8 ; 按字右移8位,即除以256 paddusw mm0, mm5 ; 饱和加到原图象:D = Alpha * (O - S) + S,(src - dst) < 0 部分 psubusw mm0, mm4 ; 饱和加到原图象D = S - Alpha * (S - O),(dst - src) > 0 部分 packuswb mm0, mm0 ; 按16位有符号数压缩为8位无符号数 ; Core End movd [edi], mm0 ; 混合结果写进目的像素 BLEND_END: add edi, 4 add esi, 4 loop BLEND_BEGIN ; 循环 add esi, nextLineOffset_src ; 加上偏移量,使定位到下行起始处 add edi, nextLineOffset_dst inc ebx mov ecx, blend_width cmp ebx, blend_height ; 若ebx小于blend_height,则转移到上面继续混合 jb BLEND_BEGIN EMMS ; 因为从mm0到mm7,这些寄存器是“借用”浮点寄存器的低64位,所以每次在用完MMX指令后一定要用EMMS指令将寄存器清空 } } void DrawBmp(UINT * pBmp, int width, int height, int x, int y) { RECT srcRect = { 0 , 0 , width - 1 , height - 1 }; RECT dstRect = { x, y, 0 , 0 }; // 边界校正 { if (srcRect.right >= width) srcRect.right = width - 1 ; if (srcRect.bottom >= height) srcRect.bottom = height - 1 ; // 超出左边界 if (dstRect.left < 0 ) { srcRect.left += - dstRect.left; dstRect.left = 0 ; } // 超出上边界 if (dstRect.top < 0 ) { srcRect.top += - dstRect.top; dstRect.top = 0 ; } int visibleW = srcRect.right - srcRect.left + 1 ; int visibleH = srcRect.bottom - srcRect.top + 1 ; // 超出右边界 if (dstRect.left + visibleW > g_GDI.m_Width) { visibleW = g_GDI.m_Width - dstRect.left; srcRect.right = srcRect.left + visibleW - 1 ; } // 超出下边界 if (dstRect.top + visibleH > g_GDI.m_Height) { visibleH = g_GDI.m_Height - dstRect.top; srcRect.bottom = srcRect.top + visibleH - 1 ; } dstRect.right = dstRect.left + (srcRect.right - srcRect.left); dstRect.bottom = dstRect.top + (srcRect.bottom - srcRect.top); if (visibleW <= 0 || visibleH <= 0 ) return ; } UINT * pSrc = pBmp + srcRect.top * width + srcRect.left; UINT * pDst = g_GDI.m_pBackBuffer + dstRect.top * g_GDI.m_Width + dstRect.left; AlphaBlend32(pDst, g_GDI.m_Width, pSrc, width, srcRect.right - srcRect.left + 1 , srcRect.bottom - srcRect.top + 1 ); } bool Init(HWND hWnd) { // 初始化g_GDI { memset( & g_GDI, 0 , sizeof (g_GDI)); g_GDI.m_hWnd = hWnd; g_GDI.m_Width = WND_WIDTH; g_GDI.m_Height = WND_HEIGHT; g_GDI.m_hMainDC = ::GetDC(hWnd); g_GDI.m_hMemoryDC = ::CreateCompatibleDC(NULL); g_GDI.m_hMainSurface = ::CreateCompatibleBitmap(g_GDI.m_hMainDC, g_GDI.m_Width, g_GDI.m_Height); g_GDI.m_pBackBuffer = (UINT * )malloc(g_GDI.m_Width * g_GDI.m_Height * 4 ); BITMAPINFO & bmpInfo = g_GDI.m_BmpInfo.m_BitmapInfo; bmpInfo.bmiHeader.biSize = sizeof (bmpInfo.bmiHeader); bmpInfo.bmiHeader.biWidth = g_GDI.m_Width; bmpInfo.bmiHeader.biHeight = - g_GDI.m_Height; bmpInfo.bmiHeader.biPlanes = 1 ; bmpInfo.bmiHeader.biBitCount = 32 ; bmpInfo.bmiHeader.biCompression = BI_BITFIELDS; * (UINT * )(bmpInfo.bmiColors + 0 ) = 0xFF0000 ; // red分量 * (UINT * )(bmpInfo.bmiColors + 1 ) = 0x00FF00 ; // green分量 * (UINT * )(bmpInfo.bmiColors + 2 ) = 0x0000FF ; // blue分量 // 将主表面选入内存DC g_GDI.m_hOldBitmap = (HBITMAP)::SelectObject(g_GDI.m_hMemoryDC, g_GDI.m_hMainSurface); } DWORD dwExStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE); if ( ! (dwExStyle & WS_EX_LAYERED)) ::SetWindowLong(hWnd, GWL_EXSTYLE, dwExStyle | WS_EX_LAYERED); return true ; } void End() { if (g_GDI.m_pBackBuffer != NULL) { free(g_GDI.m_pBackBuffer); g_GDI.m_pBackBuffer = NULL; } if (g_GDI.m_hMainSurface != NULL) { if (g_GDI.m_hOldBitmap != NULL) { ::SelectObject(g_GDI.m_hMemoryDC, g_GDI.m_hOldBitmap); g_GDI.m_hOldBitmap = NULL; } ::DeleteObject(g_GDI.m_hMainSurface); g_GDI.m_hMainSurface = NULL; } if (g_GDI.m_hMemoryDC != NULL) { ::DeleteDC(g_GDI.m_hMemoryDC); g_GDI.m_hMemoryDC = NULL; } if (g_GDI.m_hMainDC != NULL) { ReleaseDC(g_GDI.m_hWnd, g_GDI.m_hMainDC); g_GDI.m_hMainDC = NULL; } } void MainLoop() { // 清空后台像素 memset(g_GDI.m_pBackBuffer, 0 , g_GDI.m_Width * g_GDI.m_Height * 4 ); DrawBmp(g_Bmp32, g_bmpWidth, g_bmpHeight, 0 , 0 ); // Flip { ::SetDIBitsToDevice(g_GDI.m_hMemoryDC, 0 , 0 , g_GDI.m_Width, g_GDI.m_Height, 0 , 0 , 0 , g_GDI.m_Height, g_GDI.m_pBackBuffer, & g_GDI.m_BmpInfo.m_BitmapInfo, DIB_RGB_COLORS); { RECT rcWnd; ::GetWindowRect(g_GDI.m_hWnd, & rcWnd); POINT srcPos = { 0 , 0 }; POINT dstPos = { rcWnd.left, rcWnd.top }; SIZE dstSize = { rcWnd.right - rcWnd.left, rcWnd.bottom - rcWnd.top }; BLENDFUNCTION blend; blend.BlendOp = 0 ; blend.BlendFlags = 0 ; blend.AlphaFormat = AC_SRC_ALPHA; blend.SourceConstantAlpha = 255 ; ::UpdateLayeredWindow(g_GDI.m_hWnd, g_GDI.m_hMainDC, & dstPos, & dstSize, g_GDI.m_hMemoryDC, & srcPos, 0 , & blend, ULW_ALPHA); } } } LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_LBUTTONDOWN: // 使鼠标单击窗口任何一个地方都能移动整个窗口 ::SendMessageA(hWnd, WM_SYSCOMMAND, 0xF012 , 0 ); break ; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, & ps); EndPaint(hWnd, & ps); return 0 ; } break ; case WM_DESTROY: PostQuitMessage( 0 ); break ; } return DefWindowProc(hWnd, uMsg, wParam, lParam); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance, PSTR cmdLine, int nCmdShow) { // 内存泄漏检测 #if defined(DEBUG) | defined(_DEBUG) _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); #endif WNDCLASSEXA wndClass; wndClass.cbSize = sizeof (wndClass); wndClass.style = CS_HREDRAW | CS_VREDRAW; wndClass.lpfnWndProc = WndProc; wndClass.cbClsExtra = 0 ; wndClass.cbWndExtra = 0 ; wndClass.hInstance = hInstance; wndClass.hIcon = NULL; // LoadIcon(hInstance, MAKEINTRESOURCE(IDI_NORMAL)); wndClass.hCursor = LoadCursor(NULL, IDC_ARROW); wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wndClass.lpszMenuName = NULL; // MAKEINTRESOURCE(IDC_DAHUAXY2); wndClass.lpszClassName = WND_CLASSNAME; wndClass.hIconSm = NULL; // LoadIcon(wndClass.hInstance, MAKEINTRESOURCE(IDI_SMALL)); RegisterClassExA( & wndClass); DWORD wndStyle = WS_POPUP | WS_MINIMIZEBOX | WS_SYSMENU; // 无标题栏:WS_POPUP | WS_MINIMIZEBOX | WS_SYSMENU; RECT rcWnd = { 0 , 0 , WND_WIDTH, WND_HEIGHT }; AdjustWindowRect( & rcWnd, wndStyle, FALSE); HWND hWnd = CreateWindowExA( 0 , WND_CLASSNAME, WND_TITLENAME, wndStyle, (GetSystemMetrics(SM_CXSCREEN) - WND_WIDTH) / 2 , // SM_CXSCREEN:以像素为单位的屏幕的宽度 (GetSystemMetrics(SM_CYSCREEN) - WND_HEIGHT) / 2 , // SM_CYSCREEN:以像素为单位的屏幕的高度 (rcWnd.right - rcWnd.left), (rcWnd.bottom - rcWnd.top), NULL, NULL, hInstance, NULL); ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); Init(hWnd); MSG msg; memset( & msg, 0 , sizeof (msg)); while (TRUE) { if (PeekMessage( & msg, NULL, 0 , 0 , PM_REMOVE)) { if (msg.message == WM_QUIT) break ; TranslateMessage( & msg); DispatchMessage( & msg); } // 帧开始 const int constFps = 60 ; float timeInOneFps = 1000.0f / constFps; // 每秒60帧,则1帧就是约16毫秒 DWORD timeBegin = GetTickCount(); MainLoop(); // 限帧 DWORD timeTotal = GetTickCount() - timeBegin; if (timeTotal < timeInOneFps) { // Sleep(DWORD(timeInOneFps-timeTotal)); } } End(); UnregisterClassA(WND_CLASSNAME, hInstance); return 0 ; }
C/C++ code
       
       
void WuYuan::AlphaBlend32(UINT * pDstBmp, int dst_width, UINT * pSrcBmp, int src_width, int blend_width, int blend_height) { // C实现 // { // const int nextLineOffset_src = (src_width - blend_width) * 4; // 混合完一行像素后,通过加上该值,便可直接定位到下行起始像素 // const int nextLineOffset_dst = (dst_width - blend_width) * 4; // BYTE* pSrc = (BYTE*)pSrcBmp; // BYTE* pDst = (BYTE*)pDstBmp; // int below_A, below_R, below_G, below_B; // int above_A, above_R, above_G, above_B; // // for (int h=0, w=0; h<blend_height; h++) // { // for (w=0; w<blend_width; w++) // { // above_B = *pSrc++; // above_G = *pSrc++; // above_R = *pSrc++; // above_A = *pSrc++; // // if (above_A == 0) // { // pDst += 4; // continue; // } // // below_B = *pDst; // below_G = *(pDst+1); // below_R = *(pDst+2); // below_A = *(pDst+3); // // *pDst++ = below_B - (below_B-above_B)*above_A/255; // *pDst++ = below_G - (below_G-above_G)*above_A/255; // *pDst++ = below_R - (below_R-above_R)*above_A/255; // // if (below_A == 255) // *pDst++ = 255; // else // *pDst++ = below_A - (below_A-above_A)*above_A/255; // } // // pSrc += nextLineOffset_src; // pDst += nextLineOffset_dst; // } // return; // } const int nextLineOffset_src = (src_width - blend_width) * 4 ; // 混合完一行像素后,通过加上该值,便可直接定位到下行起始像素 const int nextLineOffset_dst = (dst_width - blend_width) * 4 ; __asm { mov edi, pDstBmp ; 目的像素 mov esi, pSrcBmp ; 源像素 xor ebx, ebx ; 已混合的高度 mov ecx, blend_width ; 要混合的宽度 BLEND_BEGIN: cmp dword ptr[esi], 0x00FFFFFF ; 如果alpha为0,则跳过混合部分 jna BLEND_END movd mm0, [edi] ; 把目的像素值移入mm0寄存器的低32位 movd mm1, [esi] ; 把源像素值移入mm1寄存器的低32位 ; Core Begin:result = b - (b - a) * a_alpha / 255 (a为源像素分量,b为目的像素分量) pxor mm2, mm2 ; ① 把MM2清0 punpcklbw mm0, mm2 ; 将mm0与mm2按字节交叉组合,存入mm0,mm0 = 0x00AA00BB00GG00RR punpcklbw mm1, mm2 ; 将mm1与mm2按字节交叉组合,存入mm1,mm1 = 0x00AA00BB00GG00RR movq mm3, mm1 ; ② mm3 = 0x00AA00BB00GG00RR punpckhwd mm3, mm3 ; 将高32位按16位交错排列,mm3 = 0x00AA00AA00BB00BB punpckhdq mm3, mm3 ; 将高32位按32位交错排列,mm3 = 0x00AA00AA00AA00AA movq mm4, mm0 ; ③ mm4 = 目的像素 = 0x00AA00BB00GG00RR movq mm5, mm1 ; mm5 = 源像素 = 0x00AA00BB00GG00RR psubusw mm4, mm1 ; ④ dst - src,按字饱和减,小于0为0 psubusw mm5, mm0 ; src - dst,按字饱和减,小于0为0 pmullw mm4, mm3 ; (dst - src) * alpha,若dst - src为0,则mm4为0 pmullw mm5, mm3 ; (src - dst) * alpha,若src - dst为0,则mm5为0 psrlw mm4, 8 ; 按字右移8位,即除以256 psrlw mm5, 8 ; 按字右移8位,即除以256 paddusw mm0, mm5 ; 饱和加到原图象:D = Alpha * (O - S) + S,(src - dst) < 0 部分 psubusw mm0, mm4 ; 饱和加到原图象D = S - Alpha * (S - O),(dst - src) > 0 部分 packuswb mm0, mm0 ; 按16位有符号数压缩为8位无符号数 ; Core End movd [edi], mm0 ; 混合结果写进目的像素 BLEND_END: add edi, 4 add esi, 4 loop BLEND_BEGIN ; 循环 add esi, nextLineOffset_src ; 加上偏移量,使定位到下行起始处 add edi, nextLineOffset_dst inc ebx mov ecx, blend_width cmp ebx, blend_height ; 若ebx小于blend_height,则转移到上面继续混合 jb BLEND_BEGIN EMMS ; 因为从mm0到mm7,这些寄存器是“借用”浮点寄存器的低64位,所以每次在用完MMX指令后一定要用EMMS指令将寄存器清空 } }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值