Graphics
|
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... |
#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);
}
((Colour1>>1) & 0x7F7F7F)+((Colour2>>1) & 0x7F7F7F)Masking the most significant bit of each Byte stops overflow.
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;
} }

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...
//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.
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.
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);
} }
}
};

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);
} } }
};

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);
} }
};

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;
} }
};

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);
} }
};

#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);
}
}
};


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.
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.
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.