65.9K
CodeProject 正在变化。 阅读更多。
Home

WTL 中的二维水波效果

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.66/5 (22投票s)

2011年4月28日

CPOL

4分钟阅读

viewsIcon

147938

downloadIcon

3798

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

Sample Image - maximum width is 600 pixels

引言

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

tortoisesvn about dialog effects

由于 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 像素访问性能优化,如果图像过大并且 CImageGetPixel() 成为性能瓶颈(是的,如果你的图像相对较大,确实会发生这种情况)。

更多图像渲染选项

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
© . All rights reserved.