在windows上创建完美的不规则窗口
不规则窗口方法简介
windows
上的窗口绝大多数是方形的,但是偶尔也会有一些其他形状的窗口,如圆形甚至不规则形状。这些不常见的窗口是如何做出来的呢?
有两种方法可以创建不规则窗口
1、windows
在很早的时代就支持不规则形状的窗口了。windows
提供了SetWindowRgn
函数来设置窗口的区域,还提供了一些函数来创建椭圆、圆角矩形区域,还可以自己绘制区域,但是这种方法有个缺陷,曲线的边缘有严重的锯齿。
2、使用windows2000
以后提供的透明窗口方法来创建不规则形状的窗口。当透明窗口的透明部分的透明度为0的时候次部分就会完全透明,鼠标也可以穿透。所以只要制造出有不规则的完全透明区域的窗口后,就可以形成不规则窗口。
而且可以通过边缘抗锯齿来实现平滑的窗口边缘,消除锯齿。
本文主要讲的就是第二种方法。
透明窗口API详解
之前已经有一篇文章介绍了简单的透明窗口使用vc++
创建windows
透明窗口,一些基础内容就不在赘述,请自行阅读。
此次主要用到上一篇文章中没有详细说明的UpdateLayeredWindow
函数,UpdateLayeredWindow
函数的透明和其他的透明方式不太一样,一旦使用UpdateLayeredWindow
函数就不能再使用其他的透明函数,
而且窗口的绘制也完全被UpdateLayeredWindow
接管,此时正常的绘图都不再生效,必须通过UpdateLayeredWindow
来刷新窗口的界面。
要使用UpdateLayeredWindow
刷新窗口,首先要建立一个内存dc
,然后在dc
上画图,在通过UpdateLayeredWindow
将dc
上的内容根据参数做透明处理然后刷新界面。
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
);
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
上并刷新到窗口上的代码:
void SpotlightWindow::Paint(Gdiplus::Bitmap * pImage)
{
HBITMAP old = SelectBitmap(m_memoryDC, m_memoryBitmap);
FillRect(m_memoryDC, m_windowRect, m_backgroundBrush);
Graphics graphics(m_memoryDC);
graphics.DrawImage(pImage, 0, 0, m_windowRect.Width(), m_windowRect.Height());
CSize size(m_windowRect.Width(), m_windowRect.Height()); CPoint point(0, 0);
UpdateLayeredWindow(m_mainWindow, NULL, NULL,&size, m_memoryDC, &point, 0, &m_blendFunc32bpp, ULW_ALPHA);
SelectBitmap(m_memoryDC, old);
}