Introduction
I have been a long time TortoiseSVN user impressed by the water effects shown on the About dialog banner picture. It feels like putting a glass of water on top of a picture, when you move the mouse over the picture, the water is disturbed and whirls around, meanwhile, water drops randomly fall into the water and yield small circles here and there.
Since TortoiseSVN is open source and I happened to be bored around these days, I decided to look into the implementation algorithm, and to get myself some work to do. I ported it to WTL, hence this article.
Background
The algorithm used in TortoiseSVN is based on this article, which is explanatory enough. The main changes I've made are related to porting the code from MFC to WTL as outlined below. The TortoiseSVN implementation is based on MFC, specifically, it relies on an MFC class CPictureHolder to interact with an image file. In my version, it's replaced with a newer ATL/MFC shared class CImage. Such a change brings some immediate advantages:
More image format support
While CPictureHolder supports only BMP and ICO files, CImage provides enhanced bitmap support, including the ability to load images in JPEG, GIF, BMP, and Portable Network Graphics (PNG) formats. PNG and JPG files can be loaded directly without additional decoding.
More flexible image loading
While CPictureHolder supports loading the image from resource only, CImage provides an interface to load the image from a file path.
More Efficient memory usage
Because CImage supports direct access to pixels, it saves the need to create a temporary compatible device context(DC) and an associated compatible bitmap in memory, which is adopted in the TortoiseSVN's MFC version to retrieve pixels in CPictureHolder. There's also a CImage pixel access performance optimization available on this CodeProject site, if the image is too big and CImage
's GetPixel()
becomes a performance bottleneck (yes, it happens if your image is relatively large).
More Image Rendering options
CImage supports PlgBlt()
, MaskBlt()
, AlphaBlend()
, TransparentBlt()
, which means you have options to choose while rendering the loaded image.
Aggressive refactoring has been applied to the original code such as renaming, interface changing, so that except for the core pixel manipulation code, the code is mostly new. However, refactoring is refactoring, the attribute still goes to the original author, whose license remains where it was. Only obsolete code comments are removed.
Using the Code
To use the code, add the following files into your project:
WaterEffectImplBase.h
Contains classes WaterEffectImplBase<Derived>
and WaterEffectCtl
ready to be used by client code directly.
Render.h/cpp
Contain class CRenderer
to load an image file, create a memory buffer for it and render it to a target window area after water effect manipulation, used by classes WaterEffectImplBase<Derived>
as implementation details.
WaterEffect.h/cpp
Contain class CWaterEffect
to apply the 2D water transformation to the memory buffer exposed by class CRenderer
, used by classes WaterEffectImplBase<Derived>
as implementation details.
auto_buffer.h
Contains class auto_buffer<T>,
a little class to buffer creation and destroy.
cimage_pixel_access_opt.h
Contains class CImagePixelAccessOptimizer
for CImage
pixel access optimization.
And then #include "WaterEffectImplBase.h"
, which implements two classes WaterEffectImplBase<Derived>
and WaterEffectCtl
, which means you have two ways of adding water effect to your dialog.
Method 1
The WTL developer's familiar CRTP way, derive your window class from WaterEffectImplBase
.
#include "WaterEffectImplBase.h"
class CDemoDlg: public CDialogImpl<CDemoDlg>,
public WaterEffectImplBase<CDemoDlg>
{...};
Then in order to chain the windows messages to, you need to add this line to the message map:
BEGIN_MSG_MAP(CMainDlg)
...
CHAIN_MSG_MAP(WaterEffectImplBase<CDemoDlg>)
END_MSG_MAP()
And finally, in the OnInitDialog()
method, add one line to init()
it:
LRESULT OnInitDialog(UINT , WPARAM ,
LPARAM , BOOL& )
{
init(IDB_LOGOFLIPPED, CPoint(0, 0));
return TRUE;
}
In this way, the picture is directly drawn on the dialog at the designated position.
Method 2
Alternatively, you can use WaterEffectCtl
as a control by declaring a class member of WaterEffectCtl
object.
#include "WaterEffectImplBase.h"
class CDemoDlg : public CDialogImpl<CDemoDlg>
{
public:
WaterEffectCtl we;
...};
Then, in the OnInitDialog()
method, add this following line to init()
it:
LRESULT OnInitDialog(UINT , WPARAM ,
LPARAM , BOOL& )
{
we.init(GetDlgItem(IDC_WATER2), IDB_LOGOFLIPPED, CPoint(0, 0));
return TRUE;
}
In this case, a Window handle is required for the WaterEffectCtl
object to attach to.
Synopsis
Class template<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))
Parameters
_U_STRINGorID nIDResource
, the ID of an image resource to be loaded from .rc file
const CImage& image
, an image can be pre-loaded into a CImage
object and passed in
CPoint& topleft
, the topleft position of the image to be placed, the image would be rendered with no size change
Class 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))
Parameters
HWND hWnd
, a child control to be attached to so that the image can be rendered
_U_STRINGorID nIDResource
, the ID of an image resource to be loaded from .rc file
const CImage& image
, an image can be pre-loaded in to a CImage
object and passed in
CPoint& topleft
, the topleft position of the image to be placed, the image would be rendered with no size change
History