windows
上的窗口絕大多數是方形的,但是偶爾也會有一些其他形狀的窗口,如圓形甚至不規則形狀。這些不常見的窗口是如何做出來的呢? 有兩種方法可以創建不規則窗口 1、windows
在很早的時代就支持不規則形狀的窗口了。windows
提供了SetWindowRgn
函數來設置窗口的區域,還提供了一些函數來創建橢圓、圓角矩形區域,還可以自己繪制區域,但是這種方法有個缺陷,曲線的邊緣有嚴重的鋸齒。 2、使用windows2000
以后提供的透明窗口方法來創建不規則形狀的窗口。當透明窗口的透明部分的透明度為0的時候次部分就會完全透明,鼠標也可以穿透。所以只要制造出有不規則的完全透明區域的窗口后,就可以形成不規則窗口。 而且可以通過邊緣抗鋸齒來實現平滑的窗口邊緣,消除鋸齒。 本文主要講的就是第二種方法。
之前已經有一篇文章介紹了簡單的透明窗口使用vc++
創建windows
透明窗口,一些基礎內容就不在贅述,請自行閱讀。 此次主要用到上一篇文章中沒有詳細說明的UpdateLayeredWindow
函數,UpdateLayeredWindow
函數的透明和其他的透明方式不太一樣,一旦使用UpdateLayeredWindow
函數就不能再使用其他的透明函數, 而且窗口的繪制也完全被UpdateLayeredWindow
接管,此時正常的繪圖都不再生效,必須通過UpdateLayeredWindow
來刷新窗口的界面。 要使用UpdateLayeredWindow
刷新窗口,首先要建立一個內存dc
,然后在dc
上畫圖,在通過UpdateLayeredWindow
將dc
上的內容根據參數做透明處理然后刷新界面。
UpdateLayeredWindow
有9個參數。 第一個參數是要刷新的窗口句柄,第二個是要刷新的窗口的 dc
,這個參數通常可以傳NULL
,會自動從第一個參數的窗口句柄代表的窗口中去獲取。 第三個參數是你要刷新的區域的左上坐標點,如果是全屏也可以傳NULL
,第四個參數是刷新區域的長寬,不能傳NULL
。 第五個參數就是畫好圖的內存dc
了,第六個參數是內存dc
上刷新過去的區域的左上坐標點。 第七個參數和第八個參數就是控制透明處理的參數,第七個參數設置透明色,也就是這種顏色的區域全部透明,第八個參數是設置整體透明度。 第九個參數控制是第七個參數還是第八個參數生效或者都生效。 此處要說明一下第八個參數設置的整體透明度,他只是在原有的顏色上按照參數里的比例做透明處理,如果內存dc
里的一塊區域顏色本身就是全透明的,那么無論怎么設置參數這里依然是全透明的。
使用透明窗口來制造不規則窗口的關鍵就在于怎樣制造出包含透明區域的內存dc來。
眾所周知,png
是支持透明通道的一種圖片格式,所以只要提前做好png
透明圖片,然后運行時加載到內存dc上就行了,是不是簡單爆了。
如果能用圖片那是最好啦,但是有時候如果窗口的形狀要經常變化而且形態非常多的話,就不能通過外部圖片了。只能在內存中畫出dc
的圖來。 gdi+
中的顏色支持了透明色,但是這種透明色指的是畫刷畫圖的時候和背景色做透明,最終畫在dc
上還是不透明的,并不能達到我們要的效果。那么怎么辦呢?只能使出終極辦法: 直接修改內存中的顏色數據。也就是將要透明的區域先用一種特別的顏色填充好,然后遍歷內存中的顏色數據,將這個顏色的內存數據強行修改為完全透明-——將ARGB
的代表A
通道的字節置零。 由于內存dc
并不支持遍歷內存數據,所以一般是建立一個位圖,然后在這個位圖上畫圖標記處需要透明的位置,最后遍歷位圖內存,將需要透明的位置全部摳掉。托cpu進步的福,我摳一副1080p的圖瞬間就可以完成。然后把位圖畫到dc
上去。 這里有一個小問題,因為位圖中有透明顏色,上次在dc
畫圖的時候這部分會遺留下來干擾到下次繪圖,所以要每次重畫之前把dc
內容用其他顏色給填掉。 最后就是鋸齒的問題了。如果要抗鋸齒的話就要再做一個特殊處理,首先得明白抗鋸齒的原理,可以Jan之前我寫過的gdi+的畫圖抗鋸齒原理。 用gdi+
畫出抗鋸齒的圖后,然后在遍歷的時候尋找那些由兩種顏色混合成的顏色,然后根據混合的比例相應的轉換成半透明度設置到這個像素點。通常我用白色和黑色做前景色和背景色,這樣混合變成灰色,灰色的rgb
值除以白色rgb
值的比例就是半透明度。 最后將這樣一幅位圖畫到dc
上,用UpdateLayeredWindow
函數刷新到窗口上就會形成不規則的窗口了。
畫出要被扣掉的關鍵色區域位圖的代碼:
void SpotlightWindow::DrawMaskImage(Bitmap * pImage){ Graphics graphics(pImage); graphics.FillRectangle(m_pBlackBrush, 0, 0, m_windowRect.Width(), m_windowRect.Height()); graphics.SetSmoothingMode(Gdiplus::SmoothingMode::SmoothingModeAntiAlias); graphics.FillEllipse(m_pKeyBrush, m_ellipseRect.left, m_ellipseRect.top, m_ellipseRect.Width(), m_ellipseRect.Height()); graphics.Flush(Gdiplus::FlushIntentionSync);}遍歷摳掉透明區域并對邊緣做半透明處理的代碼:
void SpotlightWindow::MakeTransparentImage(Bitmap * pImage){ Gdiplus::Rect rect(0, 0, pImage->GetWidth(), pImage->GetHeight()); Gdiplus::BitmapData bmpData; pImage->LockBits(&rect, Gdiplus::ImageLockModeWrite | Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, &bmpData); int width = pImage->GetWidth(); int height = pImage->GetHeight(); int stride = bmpData.Stride; unsigned int *pData = reinterPRet_cast<unsigned int *>(bmpData.Scan0); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { unsigned int color = *pData; if (color == WHITE_COLOR) { *pData = TRANSPARENCY_WHITE_COLOR;//設為透明色 } else if (color != BLACK_COLOR) { *pData = TransportTransparentColor(color);//根據計算結果設為把半透明顏色 } ++pData; } } pImage->UnlockBits(&bmpData);}計算半透明的代碼:
unsigned int SpotlightWindow::TransportTransparentColor(unsigned int color){ //像素顏色數據 unsigned char * pArgb = reinterpret_cast<unsigned char *>(&color); //換算出的透明度 unsigned char transparency = MAX_COLOR-*pArgb; //新的像素顏色 unsigned int transparencyColor = BLACK_COLOR; pArgb = reinterpret_cast<unsigned char *>(&transparencyColor); //設置新的像素顏色的透明度 pArgb[TRANSPARENCY_INDEX] = transparency; return transparencyColor;}將透明圖片畫到dc
上并刷新到窗口上的代碼:
新聞熱點
疑難解答