WTL 中的二维水波效果
一个 WTL 控件类,用于为图像添加水波纹效果,类似于 TortoiseSVN 的关于对话框中的效果。

引言
我长期以来一直是 TortoiseSVN 的用户,对“关于”对话框横幅图片中显示的水波纹效果印象深刻。它感觉就像在图片上覆盖了一层水,当鼠标移到图片上时,水会受到干扰并旋转,同时,水滴会随机落入水中,并在各个地方产生小圆圈。

由于 TortoiseSVN 是开源的,而且我最近恰好很闲,所以我决定研究一下它的实现算法,并给自己找点事做。我将其移植到了 WTL,因此有了这篇文章。
背景
TortoiseSVN 中使用的算法基于 这篇文章,该文章解释得很清楚。我做出的主要更改与将代码从 MFC 移植到 WTL 相关,如下所述。TortoiseSVN 的实现基于 MFC,特别是它依赖于一个 MFC 类 CPictureHolder 来与图像文件交互。在我的版本中,它被替换为更新的 ATL/MFC 共享类 CImage。这样的更改带来了一些直接的优势:
更多图像格式支持
虽然 CPictureHolder 仅支持 BMP 和 ICO 文件,但 CImage 提供了增强的位图支持,包括加载 JPEG、GIF、BMP 和可移植网络图形 (PNG) 格式图像的能力。PNG 和 JPG 文件无需额外解码即可直接加载。
更灵活的图像加载
虽然 CPictureHolder 支持仅从资源加载图像,但 CImage 提供了一个从文件路径加载图像的接口。
更高效的内存使用
由于 CImage 支持直接访问像素,因此无需创建临时兼容设备上下文 (DC) 和关联的内存兼容位图,而这是 TortoiseSVN 的 MFC 版本中用来从 CPictureHolder 获取像素的方法。在此 CodeProject 站点上还有一个 CImage 像素访问性能优化,如果图像过大并且 CImage
的 GetPixel()
成为性能瓶颈(是的,如果你的图像相对较大,确实会发生这种情况)。
更多图像渲染选项
CImage 支持 PlgBlt()
、MaskBlt()
、AlphaBlend()
、TransparentBlt()
,这意味着你在渲染加载的图像时有多种选择。
对原始代码进行了积极的重构,例如重命名、接口更改,因此除了核心像素处理代码外,大部分代码都是全新的。但是,重构就是重构,功劳仍然归于原作者,其许可证保持不变。仅删除了过时的代码注释。
Using the Code
要使用代码,请将以下文件添加到你的项目中:
WaterEffectImplBase.h
包含 WaterEffectImplBase<Derived>
和 WaterEffectCtl
类,可供客户端代码直接使用。
Render.h/cpp
包含 CRenderer
类,用于加载图像文件,为其创建内存缓冲区,并在应用水波纹效果后将其渲染到目标窗口区域,由 WaterEffectImplBase<Derived>
类作为实现细节使用。
WaterEffect.h/cpp
包含 CWaterEffect
类,用于将 2D 水波纹变换应用于 CRenderer
类暴露的内存缓冲区,由 WaterEffectImplBase<Derived>
类作为实现细节使用。
auto_buffer.h
包含 auto_buffer<T>
类,这是一个用于缓冲创建和销毁的小类。
cimage_pixel_access_opt.h
包含 CImagePixelAccessOptimizer
类,用于 CImage
像素访问优化。
然后 #include "WaterEffectImplBase.h"
,它实现了两个类 WaterEffectImplBase<Derived>
和 WaterEffectCtl
,这意味着你有两种方式为你的对话框添加水波纹效果。
方法 1
WTL 开发者熟悉的 CRTP 方法,从 WaterEffectImplBase
派生你的窗口类。
#include "WaterEffectImplBase.h"
class CDemoDlg: public CDialogImpl<CDemoDlg>,
public WaterEffectImplBase<CDemoDlg>
{...};
然后,为了将窗口消息链接过去,你需要在此消息映射中添加此行:
BEGIN_MSG_MAP(CMainDlg)
...
CHAIN_MSG_MAP(WaterEffectImplBase<CDemoDlg>)
END_MSG_MAP()
最后,在 OnInitDialog()
方法中,向 init()
添加一行:
LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/,
LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
// ...
//draw on the dialog directly starting from the topleft
init(IDB_LOGOFLIPPED, CPoint(0, 0));
// ...
return TRUE;
}
这样,图片将直接在对话框的指定位置绘制。
方法 2
或者,你可以将 WaterEffectCtl
用作控件,方法是声明一个 WaterEffectCtl
对象的类成员。
#include "WaterEffectImplBase.h"
class CDemoDlg : public CDialogImpl<CDemoDlg>
{
public:
WaterEffectCtl we;
...};
然后,在 OnInitDialog()
方法中,向 init()
添加以下行:
LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/,
LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
// ...
// draw on the member static control
we.init(GetDlgItem(IDC_WATER2), IDB_LOGOFLIPPED, CPoint(0, 0));
// ...
return TRUE;
}
在这种情况下,需要一个窗口句柄供 WaterEffectCtl
对象附加。
摘要
类模板<class Derived> class WaterEffectImplBase
void init(_U_STRINGorID nIDResource, const CPoint& topleft = CPoint(0,0))
void init(const CImage& image, const CPoint& topleft = CPoint(0,0))
参数
_U_STRINGorID nIDResource
,这是要从 *.rc 文件加载的图像资源的 ID。const CImage& image
,可以预先将图像加载到CImage
对象中并传递。CPoint& topleft
,图像放置的左上角位置,图像将以原始尺寸渲染。
类 class WaterEffectCtl
void init(HWND hWnd, _U_STRINGorID nIDResource, const CPoint& topleft = CPoint(0,0))
void init(HWND hWnd, const CImage& image, const CPoint& topleft = CPoint(0,0))
参数
HWND hWnd
,一个要附加的子控件,以便可以渲染图像。_U_STRINGorID nIDResource
,这是要从 *.rc 文件加载的图像资源的 ID。const CImage& image
,可以预先将图像加载到CImage
对象中并传递。CPoint& topleft
,图像放置的左上角位置,图像将以原始尺寸渲染。
历史
- 首次发布,2011-4-27