Site MapGraphics

If you have Cookies enabled,
you can check to see if any files you have downloaded
have been updated since you downloaded them
by clicking here.


Each of the Algorithms have been implemented as C++ classes using Microsoft Visual C++ V6.

Microsoft Foundation Classes (MFC) have been used where necessary.
Non MFC folk can remove the pragma statements and use the following info to adjust the code:
typedef unsigned char  BYTE;
typedef unsigned short WORD;
typedef unsigned long  DWORD;
My intention is to provide simple, free access to useable solutions to common but difficult programming problems.

I have treated the creation of each class as a puzzle and hopefully have come up with good solutions.

If you know any better solutions then please e-mail me so that everyone can share your wisdom!

Be aware that I use a non-standard "Coding Style"... and its infectious...

High Speed Fundamental Graphics

Draw to RAM, Blit to Screen

If you want to re-invent the wheel and do your own graphics primitives, this is for you!
To draw to the screen really fast you need an area of memory you can access directly and Blit to the screen.
The fundamental thing you want to do is be able to call functions SetPixel(x,y,Colour) and GetPixel(x,y).
Its incredibly slow to do this using operating-system setpixel/getpixel or even writing directly to your Video Card: they're just not designed to be used that way.
Instead you're expected to write to your own copy of the screen in RAM memory and use a Video Card function called "Blit" which copies your logical screen to the physical screen.
The word "Blit" came from "Bit Block Transfer".
Just how you go about creating and blitting depends on your operating system and isn't always obvious.
Windows, for example, has a Blit function, but this transfers areas of the screen to RAM or other areas of screen, which isn't what we want.
It turns out that the function to use is SetDIBitsToDevice.
Since the easiest way to draw is in True Colour, that is what this system uses.
Hopefully the code in the following class will be sufficient for you to encapsulate the same behaviour whatever operating system you are using.

The Pixel Block of RAM that you draw to, is an array of DWORDs each holding the colour in the format 0xRRGGBB
RR the Red, GG the Green and BB the Blue (the most significant byte isn't used). This is the opposite way round to what the RGB() Macro provides.
The only other important data structure is a BITMAPINFO with its bmiHeader.biCompression set to BI_RGB.
Code is included to initialise the Pixel Block by blitting to the PixelBlock.
You can also Save the PixelBlock as a True Colour Bitmap File (.bmp).

Heres an example using an Owner-draw button as the DC to draw to:
#include "PixelBlock.h"
void CMyDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) {
  if(nIDCtl==IDC_Pic) {
    CPixelBlock PixelBlock(100,100);
    PixelBlock.Rectangle(10,20,25,36,0xFF8000);
    PixelBlock.Rectangle(20,20,35,26,0x0080FF);
    PixelBlock.Rectangle(00,10,25,26,0x00FFFF);
    PixelBlock.Paint(lpDrawItemStruct->hDC, 10,10);
  }
  CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);
}

Blending Colours

When using SetPixel, the original colour of the Pixel being set is overwritten.
If, however, you wanted to draw an object which appeared transparent, you'd want to mix the existing colour with the new colour.
An objects Transparency is referred to as its Alpha Channel hence the drawing of a transparent object refers to Alphablending.
Transparency, like each of the R,G and B Channels, is a Byte value, 0 represents no Transparency; 255 means totally Transparent (invisible).
This allows certain optimisations: if Transparency is 0 we can just use SetPixel; if Transparency is 255 we don't need to do anything at all!
When mixing colours evenly (50% Transparency) you are trying to find the average of each RGB component ((R1+R2)/2, (G1+G2)/2, (B1+B2)/2)
For our DWORD Colours this can be done very efficiently once we accept that rounding errors are insignificant:
Use bitwise operations to replicate the sum: RBG1/2+RGB2/2
((Colour1>>1) & 0x7F7F7F)+((Colour2>>1) & 0x7F7F7F)
Masking the most significant bit of each Byte stops overflow.
The general equation for all levels of Transparency for each Channel is (R2*Transparency)/256 + (R1*(255-Transparency))/256
We can apply a few optimisations like >>8 instead of /256, and we can do this just once at the end of our equation because we know that the most significant Byte is unused.
Also, 255-Transparency is the same as ~Transparency for our purposes, and this is faster for the processor too.
  DWORD MixColours(DWORD Colour1, DWORD Colour2) {return((Colour1>>1) & 0x7F7F7F)+((Colour2>>1) & 0x7F7F7F);}
  DWORD AlphaBlend(DWORD Paper, DWORD Paint, BYTE Transparency) {
    switch(Transparency) {
      case   0: return Paper;
      case  64: return MixColours(Paper,MixColours(Paper,Paint));
      case 128: return MixColours(Paint,Paper); // When hard-coding 50% transparency, use 128 cuz its faster than 127.
      case 191: return MixColours(Paint,MixColours(Paint,Paper));
      case 255: return Paint;
      default : register BYTE PaperTransparency=~Transparency;
                return (((((Paint&0x0000FF)*Transparency)+((Paper&0x0000FF)*PaperTransparency))&0x0000FF00)
                      | ((((Paint&0x00FF00)*Transparency)+((Paper&0x00FF00)*PaperTransparency))&0x00FF0000)
                      | ((((Paint&0xFF0000)*Transparency)+((Paper&0xFF0000)*PaperTransparency))&0xFF000000))>>8;
  } }

So now you can draw a transparent Rectangle with code as simple as this:
  void Rectangle(WORD x, WORD y, WORD dx, WORD dy, DWORD Colour, BYTE Transparency) {
    if(x+dx<Width && y+dy<Height) {
      DWORD* Line=&Bits[x+Width*y];
      for(WORD i=0; i<dy; ++i) {
        DWORD* Pixel=Line;
        for(WORD j=0; j<dx; ++j) *Pixel++=AlphaBlend(*Pixel, Colour, Transparency);
        Line+=Width;
    } }
  }
You can use the same AlphaBlend function to produce Gradient Fills and to alter the Brightness of images...
For Gradient Fills the colour of the current position is just AlphaBlend(StartColour, EndColour, Position);

//Fades a rectangle from one colour to another. If Top2Bottom is false the fade is Left to Right:
  void Rectangle(WORD x, WORD y, WORD dx, WORD dy, DWORD SttColour, DWORD EndColour, bool Top2Bottom) {
    if(x+dx<Width && y+dy<Height) {
      DWORD* Line=&Bits[x+Width*y];
      for(WORD i=0; i<dy; ++i) {
        DWORD* Pixel=Line;
        BYTE Transparency=(i<<8)/dy;
        for(WORD j=0; j<dx; ++j) *Pixel++=AlphaBlend(SttColour, EndColour, Top2Bottom ? Transparency : (j<<8)/dx);
        Line+=Width;
    } }
  }
To alter the Brightness of an image you're mixing the original image with Black to make it darker, or White to make it brighter.
The Transparency value determines how much darker or brighter your final image will be.

Antialiasing

When drawing lines at an angle on low resolution screens the lines appear jagged:
Antialiasing is the process which draws a line using Alphablending to smooth the edges of the line and make the "Jaggies" less obvious:

Drawing Lines


Antialiasing has no effect on horizontal, vertical or 45° lines because each pixel is exactly on the line.
All other lines' Pixels are drawn to the nearest Pixel. When drawing an antialiased line we simply calculate the how close the pixel is to the real line and draw the pixel at a suitable intensity.
The usual algorithm used for drawing lines is "Wu Antialiasing" which draws lines extremely fast.

The following class is the first of the Stencil Suite of classes which are intended to be used with CPixelBlock.
You are expected to Create your own class derived from CPixelBlock and any of the Stencil classes that you wish to use.
You need your class to provide interfaces for the abstract functions in the Stencil classes as in the following example:
class CDrawingBoard : public CPixelBlock, public CLineStencil, public CCircleStencil {
  WORD   GetWidth  ()               const {return CPixelBlock::GetWidth();}
  DWORD* GetPointer(WORD x, WORD y) const {return CPixelBlock::GetPointer(x,y);}
  void   SetPixel  (WORD x, WORD y, DWORD Colour) {CPixelBlock::SetPixel(x,y, Colour);}
  void   BlendPixel(WORD x, WORD y, DWORD Colour, BYTE Transparency) {CPixelBlock::BlendPixel(x,y, Colour, Transparency);}
  ...
};
This particular class draws directly to the CPixelBlock for the lines which don't need antialiasing.
You may prefer to put those routines actually inside the PixelBlock class rather than use GetPointer which exposes the PixelBlocks Data!
(My code has the Line function as a member of CPixelBlock, and CPixelBlock has no GetPointer function).
class CLineStencil {
  virtual WORD GetWidth() const =0;
  virtual DWORD* GetPointer(WORD x, WORD y) const =0;
  virtual void   SetPixel(WORD x, WORD y, DWORD Colour) =0;
  virtual void BlendPixel(WORD x, WORD y, DWORD Colour, BYTE Transparency) =0;
  void Swap(WORD& a, WORD& b) {WORD t=a; a=b; b=t;}
public:
  void Line(WORD x0, WORD y0, WORD x1, WORD y1, DWORD Colour) {
    int dx, dy, xDir;
    if(y0>y1) {
      Swap(y0,y1);
      Swap(x0,x1);
    }
    SetPixel(x0,y0, Colour); //First and last Pixels always get Set:
    SetPixel(x1,y1, Colour);
    dx=x1-x0;
    dy=y1-y0;
    if(dx>=0) xDir=1;
    else {
      xDir=-1;
      dx=-dx;
    }
    if(dx==0) { // vertical line
      for(DWORD* ptr=GetPointer(x0,y0); dy--; ptr+=GetWidth()) *ptr=Colour;
      return;
    }
    if(dy==0) { // horizontal line
      if(x0>x1) {Swap(x0,x1);}
      for(DWORD* ptr=GetPointer(x0,y0); dx--; *ptr++=Colour);
      return;
    }
    if(dx==dy) { // diagonal line.
      for(DWORD* ptr=GetPointer(x0,y0); dy--; ptr+=GetWidth()+xDir) *ptr=Colour;
      return;
    }
// line is not horizontal, diagonal, or vertical: use Wu Antialiasing:
    DWORD ErrorAcc=0;
    BYTE Transparency;
    if(dy>dx) { // y-major line
      DWORD ErrorAdj=((DWORD)dx<<16) / (DWORD)dy;
      if(xDir<0) {
        while(--dy) {
          ErrorAcc+=ErrorAdj;
          ++y0;
          x1=x0-(WORD)(ErrorAcc>>16);
          Transparency=(BYTE)(ErrorAcc>>8);
          BlendPixel(x1  , y0, Colour, ~Transparency);
          BlendPixel(x1-1, y0, Colour,  Transparency);
        }
      }else{
        while(--dy) {
          ErrorAcc+=ErrorAdj;
          ++y0;
          x1=x0+(WORD)(ErrorAcc>>16);
          Transparency=(BYTE)(ErrorAcc>>8);
          BlendPixel(x1     , y0, Colour, ~Transparency);
          BlendPixel(x1+xDir, y0, Colour,  Transparency);
      } }
    }else{ // x-major line
      DWORD ErrorAdj=((DWORD)dy<<16) / (DWORD)dx;
      while(--dx) {
        ErrorAcc+=ErrorAdj;
        x0+=xDir;
        y1=y0+(WORD)(ErrorAcc>>16);
        Transparency=(BYTE)(ErrorAcc>>8);
        BlendPixel(x0, y1  , Colour, ~Transparency);
        BlendPixel(x0, y1+1, Colour,  Transparency);
    } }
  }
};

Drawing Circles

The following class draws circles extremely fast using the "Midpoint" algorithm.

class CCircleStencil {
  virtual void BlendPixel(WORD x, WORD y, DWORD Colour, BYTE Transparency) =0;
public:
  void Circle(WORD xCenter, WORD yCenter, WORD Radius, DWORD Colour, BYTE Transparency=255) {
    int x=0;
    int y=Radius;
    int p=(5-(Radius<<2))>>2; //Midpoint Algorithm
    BlendPixel(xCenter  , yCenter+y, Colour, Transparency);
    BlendPixel(xCenter  , yCenter-y, Colour, Transparency);
    BlendPixel(xCenter+y, yCenter  , Colour, Transparency);
    BlendPixel(xCenter-y, yCenter  , Colour, Transparency);
    while(x++<y) {
      if(p<0) p+=1+(x<<1);
      else    p+=1+((x- --y)<<1);
      if(x<y) {
        BlendPixel(xCenter+x, yCenter+y, Colour, Transparency);
        BlendPixel(xCenter-x, yCenter+y, Colour, Transparency);
        BlendPixel(xCenter+x, yCenter-y, Colour, Transparency);
        BlendPixel(xCenter-x, yCenter-y, Colour, Transparency);
        BlendPixel(xCenter+y, yCenter+x, Colour, Transparency);
        BlendPixel(xCenter-y, yCenter+x, Colour, Transparency);
        BlendPixel(xCenter+y, yCenter-x, Colour, Transparency);
        BlendPixel(xCenter-y, yCenter-x, Colour, Transparency);
      }else if(x==y) {
        BlendPixel(xCenter+x, yCenter+y, Colour, Transparency);
        BlendPixel(xCenter-x, yCenter+y, Colour, Transparency);
        BlendPixel(xCenter+x, yCenter-y, Colour, Transparency);
        BlendPixel(xCenter-x, yCenter-y, Colour, Transparency);
  } } }
};

Drawing Bezier Curves


The following class draws Bezier curves using an inefficient technique but minimal code.
The curve is drawn as evenly spaced dots, like a piece of string with evenly spaced knots in it...
If you place your string in the curve of your choice on a grid, some grid squares (Pixels on the screen) will have two knots in and some will have none...
This class guesses the value of Step in the hope that it draws enough dots so that there are no breaks in the line.
Tight bends will have their Pixels overwritten many times.
The curve is drawn from the Start Point to the End Point with the line initially pointing towards the Control Point.
The further the (first) Control Point is away from the Start Point, the more the Curve tends towards the Control Point.
These are often used to render Fonts.
class CCurveStencil {
  virtual void SetPixel(WORD x, WORD y, DWORD Colour) =0;
public:
// Bezier Curve: Start Point, Control Point, EndPoint
  void Curve(WORD xStt, WORD yStt, WORD xCtl, WORD yCtl, WORD xEnd, WORD yEnd, DWORD Colour) {
    double Step=0.4/((max(max(xStt,xCtl),xEnd)-min(min(xStt,xCtl),xEnd))+(max(max(yStt,yCtl),yEnd)-min(min(yStt,yCtl),yEnd)));
    WORD xOld=xEnd;
    WORD yOld=yEnd;
    for(double mu=0; mu<1; mu+=Step) {
      double mu2=mu*mu;
      double mum1=1-mu;
      double mum12=mum1*mum1;
      WORD x=(WORD)(0.5+xStt*mum12+2*xCtl*mum1*mu+xEnd*mu2);
      WORD y=(WORD)(0.5+yStt*mum12+2*yCtl*mum1*mu+yEnd*mu2);
      if((x!=xOld)||(y!=yOld)) SetPixel(xOld=x, yOld=y, Colour);
  } }

// Bezier Curve: Start Point, Start Control Point, End Point, End Control Point
  void Curve(WORD xStt, WORD yStt, WORD xSC, WORD ySC, WORD xEnd, WORD yEnd, WORD xEC, WORD yEC, DWORD Colour) {
    double Step=0.4/((max(max(max(xStt,xSC),xEC),xEnd)-min(min(min(xStt,xSC),xEC),xEnd))+(max(max(max(yStt,ySC),yEC),yEnd)-min(min(min(yStt,ySC),yEC),yEnd)));
    WORD xOld=xEnd;
    WORD yOld=yEnd;
    for(double mu=0; mu<1; mu+=Step) {
      double mum1=1-mu;
      double mum13=mum1*mum1*mum1;
      double mu3=mu*mu*mu;
      WORD x=(WORD)(0.5+mum13*xStt+3*mu*mum1*mum1*xSC+3*mu*mu*mum1*xEC+mu3*xEnd);
      WORD y=(WORD)(0.5+mum13*yStt+3*mu*mum1*mum1*ySC+3*mu*mu*mum1*yEC+mu3*yEnd);
      if((x!=xOld)||(y!=yOld)) SetPixel(xOld=x, yOld=y, Colour);
  } }
};

Drawing the Mandelbrot Set


The following class draws the Mandelbrot set, a distinctive shape created by mathematicians for their own amusement:
class CMandelbrotStencil {
  virtual void SetPixel(WORD x, WORD y, DWORD Colour) =0;
public:
// l=Lower, u=Upper bounds of set to draw. d=Scale Smaller number=Bigger Picture.
  void Mandelbrot(double lx=-2, double ux=0.6, double ly=-1.2, double uy=1.2, double d=0.02) {
    int px=0;
    for(double x=lx; x<ux; x+=d) {
      int py=0;
      for(double y=ly; y<uy; y+=d) {
        int iMax=50;
        double X=x;
        double Y=y;
        for(int i=0; i<iMax; ++i) {
          double x1=X*X-Y*Y+x;
          double y1=2*X*Y+y;
          if(x1*x1+y1*y1>=4) break;
          X=x1;
          Y=y1;
        }
        int c=iMax-i;
        SetPixel(px, py++, ((((c*5)<<8)|(c<<4))<<8)|(c*12));
      }
      ++px;
  } }
};

Drawing the Fractal Fern


This class uses Fractal principles to create a picture of a Fern Leaf.
Not exactly an optimum way of drawing a leaf, but fascinating for its mystery!
class CFernStencil {
  virtual void BlendPixel(WORD x, WORD y, DWORD Colour, BYTE Transparency) =0;
public:
  CFernStencil() {}
  void Fern(WORD x0, WORD y0, int Scale=20) {
    double x=0,y=0,t;
    long LastRand=1;
    for(long g=100000; g; --g) {
      int n=(int)((LastRand=LastRand*214013L+2531011L) & 0x7FFF); // Random number generator.
           if(n< 328) {t= 0            ; y=        0.16*y     ;}
      else if(n<2621) {t= 0.2 *x-0.26*y; y= 0.28*x+0.22*y+1.6 ;}
      else if(n<4915) {t=-0.15*x+0.28*y; y= 0.26*x+0.24*y+0.44;}
      else            {t= 0.85*x+0.04*y; y=-0.04*x+0.85*y+1.6 ;}
      BlendPixel((WORD)(x0+(x=t)*Scale),(WORD)(y0-y*Scale), 0x00D000, 64);
  } }
};

Drawing the Seed Head


This class uses Nature's Golden Ratio to create a picture of a Seed Head: Sunflower seeds, dandelion, fir cone, pineapple etc.
#include <math.h>

class CSeedHeadStencil {
  virtual void Disc(WORD xCenter, WORD yCenter, WORD Radius, DWORD Colour) =0; // Antialiased, Filled apart from centre pixel:
public:
  CSeedHeadStencil() {}
  void Seeds(WORD x0, WORD y0, DWORD Colour, short SeedRadius=6, int Seeds=987) { // Seeds should be a number from the Fibonacci series
    const double  PI=3.1415926535897932384626433832795;
    const double Phi=1.6180339887498948482045868343656; // The Golden Ratio
    const double  dA=Phi/PI;
    double Angle= dA;
    for(int Seed=Seeds; Seed--; Angle+=(Phi-1)*2*PI) { // Move 0.618 of a circle round each time
      double r=sqrt(Seed)*SeedRadius*dA; // Position Radius is sqrt of the seed number scaled to suit SeedRadius
      Disc(x0+(WORD)(0.5+r*cos(Angle)), y0+(WORD)(0.5+r*sin(Angle)), SeedRadius-SeedRadius*Seed/Seeds, Colour);
    }
  }
};

Effects

Pixels turn to Sand


CSandInterface makes the contents of a window collapse as if it were turned to sand.
To use it, either draw first(and only once!) and call the Draw function afterwards
or derive a class from it which implements the InitDC and InitPixelBlock functions
and put your drawing code in there.
This example uses an Owner-draw Button in a Dialog:
class CSand : public CSandInterface {
public:
  void InitDC(HDC hDC, const CRect& Rect) {
    FillRect(hDC, &Rect, CreateSolidBrush(GetSysColor(COLOR_3DFACE))); //Draw in dialogs background colour
    DrawText(hDC, "Hello",-1, (LPRECT)&Rect, DT_SINGLELINE|DT_CENTER|DT_VCENTER);
  }
  void InitPixelBlock() {
    Rectangle(10,20,10,20,0xCAFE69);
    Rectangle(20,10,50,10,0x69CAFE);
  }
};
Then in the Dialog Header have:
  CSand Sand;
and in the .cpp file use ClassWizard to add an OnDrawItem handler like this:
void CMyDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) {
  if(nIDCtl==IDC_Pic) Sand.Draw(lpDrawItemStruct->hDC, lpDrawItemStruct->rcItem, 1000); //Delay 1 second (1000ms) before collapsing
  CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);
}
You can save all the frames as bitmaps using the Draw function with the SaveFiles parameter set to true.
they get saved in the current Directory as t000.bmp upwards.

If its too fast (for small buttons etc), call SetSlow().

Bear in mind that this algorithm needs to know what the background colour is (ie. what colour Pixel is _not_ sand...
By default it uses the System Dialog Background Colour, if you're using something else use the SetBGColor function.
The following program demonstrates the sand effect on a picture created with the other articles on this page:

Effects

Pixels turn to Water


CWaterInterface makes the contents of a window appear to be under water.
The constant speed is a consequence of the algorithm.
Most implementations of this algorithm use large arrays, DirectX or OpenGL...
This doesn't need DirectX or OpenGL, all drawing is done using CPixelBlock.
The two height Arrays are chars so the overhead is one Byte for every two pixels.
The Lighting effect is done by altering the pixel's brightness using CPixelBlock::BlendPixel(...) to Blend the Pixel with Black or White.

To use it, either draw first(and only once!) and call the Draw function afterwards
or derive a class from it which implements the InitDC and InitPixelBlock functions
and put your drawing code in there.

This example uses an Owner-draw Button (IDC_Water) in a Dialog:
class CAboutButton : public CButton, public CWaterInterface {
  LPDRAWITEMSTRUCT pDrawItemStruct;
  WORD LastX, LastY;
public:
  CAboutButton() : CWaterInterface(false,true) {}
  virtual ~CAboutButton() {}
private:
  bool InitDC(HDC hDC, WORD Width, WORD Height) { // Draw what you want on the DC and then return true;
    CRect Rect(0,0,Width,Height);
    FillRect(hDC, &Rect, CreateSolidBrush(GetSysColor(COLOR_3DFACE))); //Draw in dialogs background colour
    DrawEdge(hDC, (LPRECT)&Rect, EDGE_RAISED, BF_RECT);
    CString Title;
    GetWindowText(Title);
    DrawText(hDC, Title,-1, (LPRECT)&Rect, DT_SINGLELINE|DT_CENTER|DT_VCENTER);
    return true; // Always
  }
  void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) {Draw(lpDrawItemStruct->hDC);}
  DECLARE_MESSAGE_MAP()
  afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
  afx_msg void OnMouseMove  (UINT nFlags, CPoint point);
};

//------------- AboutButton -----------------

BEGIN_MESSAGE_MAP(CAboutButton, CButton)
  ON_WM_LBUTTONDOWN()
  ON_WM_MOUSEMOVE()
END_MESSAGE_MAP()

void CAboutButton::OnLButtonDown(UINT nFlags, CPoint point) {CButton::OnLButtonDown(nFlags, point); Dip((WORD)point.x, (WORD)point.y, 10, 127);}
void CAboutButton::OnMouseMove  (UINT nFlags, CPoint point) {
  if(IsDripping()) Straight(LastX, LastY, (WORD)point.x, (WORD)point.y,1);
  LastX=(WORD)point.x;
  LastY=(WORD)point.y;
}

Then in the Dialog Header have:
  CAboutButton About;
You need to size the PixelBlock in OnInitDialog: BOOL CPhidippusDlg::OnInitDialog() { CDialog::OnInitDialog(); Water.SubclassDlgItem(IDC_Water, this); CRect Rect; Water.GetClientRect(&Rect); Water.Set(Rect.Width(), Rect.Height()); ... } void CMyDlg::OnBnClickedAbout() { About.StopRaining(); CDialog(IDD_ABOUTBOX).DoModal(); About.StartRaining(m_hWnd); } You can stop flashing as Windows erases control backgrounds by setting the "Clip Children" Property for the Dialog.
You can save all the frames as bitmaps using the Draw function with the SaveFiles parameter set to true.
they get saved in the current Directory as t000.bmp upwards.

To Draw Directly on the Dialog Window do the following: Create a Class that is the "paper" you will draw to derived from CWaterInterface: class CMyGame : public CWaterInterface { ... } Declare one in the dialog .cpp file as a global:
  CMyGame MyGame;

  void CMyDlg::OnPaint() {
    CPaintDC dc(this); // device context for painting
    if(IsIconic()) {
      CRect Rect;
      GetClientRect(&Rect);
      SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
      int cxIcon=GetSystemMetrics(SM_CXICON);
      int cyIcon=GetSystemMetrics(SM_CYICON);
      int x=(Rect.Width ()-cxIcon+1)/2;
      int y=(Rect.Height()-cyIcon+1)/2;
      dc.DrawIcon(x, y, m_hIcon);
    }else MyGame.Draw(dc.GetSafeHdc()); // <<<<<<<<<<<< Water Drawn Here!
  }

  BOOL CMyDlg::OnEraseBkgnd(CDC* pDC) {return TRUE;} // Don't erase - it flashes!

  void CMyDlg::OnLButtonDown(UINT nFlags, CPoint point) {
    MyGame.OnClick((WORD)point.x, (WORD)point.y);
    CDialog::OnLButtonDown(nFlags, point);
  }

// You need to tell the PixelBlock its size which for complete windows is best done in OnSize.
// For re-sizing windows you need to stop the water before resizing and restart it afterwards:
  void CMyDlg::OnSize(UINT nType, int cx, int cy) {
    MyGame.StopRaining();
    MyGame.Resample(); // Calls your InitDC and InitPixelBlock again next redraw
    MyGame.Set(cx,cy); // Resizes the Pixel Block
	  CDialog::OnSize(nType, cx, cy);
    MyGame.StartRaining(m_hWnd);
  }
If you pop up other dialogs like the about box you will need to stop the water first:
  void CMyDlg::OnSysCommand(UINT nID, LPARAM lParam) {
    if((nID & 0xFFF0)==IDM_ABOUTBOX) {
      MyGame.StopRaining();
        CDialog(IDD_ABOUTBOX).DoModal();
      MyGame.StartRaining(m_hWnd);
    }else CDialog::OnSysCommand(nID, lParam);
  }
SetDripping() keeps the animation going with a central drip.
SetRaining() keeps the animation going with random drips.
The following program demonstrates the water effect on a picture created with the other articles on this page:

If anyone thinks its worth me maintaining or updating any of the above, please e-mail me.