Table of Contents
Jagged lines are major obstacles in achieving professional displays of raster graphics. Antialiasing can produce very smooth lines and provide a stylish appearance. You must already have observed good-looking antialiased diagrams in PowerPoint 2003. They look very smooth. Although GDI+ offers antialiasing, most computers may not have its redistributable. With .NET, you get antialiasing, but again, most computers may not have the .NET framework available. So I prefer writing "Windows portable" programs in VC++ 6. Hence, here is an MFC version of the Wu Antialiasing Algorithm.
Research has led to the creation of several techniques for antialiasing. Graphics textbooks like Foley, Van Dam discuss the Gupta-Sproul and related algorithms. For fast antialiasing, Xiaolin Wu invented an algorithm called by his name: Wu Antialiasing. Michael Abrash's Graphics Programming Black Book gives excellent treatment of this algorithm. Hugo Elias also has an excellent article on the matter; I strongly recommend reading this one. However, neither have MFC-usable code, so I have implemented their code on MFC.
I wrote a simple WuCircle
routine to generate a circle made up of line segments. Now let's see the difference that we achieve by using this implementation. Figure 2 shows the zoomed views of the above spokes. The image on the left side shows normal drawing. The jagged edges are clearly visible in it. The right-side image is the antialiased drawing and we can see the smoothing achieved using "GrayScale" intensities.
You can reuse the function DrawWuLine
. Just call it anywhere you need to draw an antialiased line.
void DrawWuLine (CDC *pDC, short X0, short Y0, short X1, short Y1,
short BaseColor, short NumLevels, unsigned short IntensityBits);
There is a simple routine for circle generation, which you can reuse. Internally, it calls the line routine explained above.
void DrawWuCirlce (CDC * pDC, int x, int y, int r);
Both functions can be easily modified to use HDC
instead of CDC *
, in case you are writing non-MFC Win32 applications.
This application generates some spokes and concentric circles using both "normal" GDI (non-antialiased) and antialiased line routines. You can press the "a" key to toggle the animation. The animated wheels show a clear distinction between antialiased and normal line drawing.
RotorThread
is a routine that animates spooked wheels. It uses a memory bitmap and a device context. At ~20 fps (frames per second), it rotates the wheels on a memory bitmap. Using BitBlt
, the drawing is brought on the main window.
Pressing "a" again terminates the thread.
UINT RotorThread (LPVOID lpVoid)
{
bool * pbStop = (bool *) lpVoid;
CWnd * pWnd = AfxGetMainWnd();
CDC * pDC = pWnd->GetDC();
CRect rect;
pWnd->GetClientRect (&rect);
CDC memDC;
memDC.CreateCompatibleDC (pDC);
CBitmap bitmap;
bitmap.CreateCompatibleBitmap (pDC,
rect.Width(), rect.Height());
memDC.SelectObject (&bitmap);
CFont font;
font.CreatePointFont (185, "Verdana", &memDC);
memDC.SelectObject (&font);
memDC.SetTextAlign (TA_CENTER);
float phase = 0.0f;
while (!(*pbStop))
{
memDC.Rectangle (0, 0, rect.Width(), rect.Height());
memDC.TextOut (100, 15, "Normal");
memDC.TextOut (350, 15, "Anti-aliased");
short x, y;
for (float theta= phase; theta<
360+phase; theta += 10 )
{
x = (short)(100.0*cos(theta*3.14/180.0)+355.0);
y = (short)(-100.0*sin(theta*3.14/180.0)+155.0);
DrawWuLine (&memDC,x, y, 355, 155, 0, 256, 8);
memDC.MoveTo (x-240,y);
memDC.LineTo (115,155);
}
pDC->BitBlt (0, 0, rect.Width(), rect.Height(),
&memDC, 0, 0, SRCCOPY);
phase += 1;
::Sleep (67);
}
font.DeleteObject();
bitmap.DeleteObject();
memDC.DeleteDC();
pWnd->ReleaseDC (pDC);
return 0;
}
Here is the implementation of the DrawWuLine
function:
void DrawWuLine (CDC *pDC, short X0, short Y0, short X1, short Y1,
short BaseColor, short NumLevels, unsigned short IntensityBits)
{
unsigned short IntensityShift, ErrorAdj, ErrorAcc;
unsigned short ErrorAccTemp, Weighting, WeightingComplementMask;
short DeltaX, DeltaY, Temp, XDir;
if (Y0 > Y1) {
Temp = Y0; Y0 = Y1; Y1 = Temp;
Temp = X0; X0 = X1; X1 = Temp;
}
DrawPixel(pDC,X0, Y0, BaseColor);
if ((DeltaX = X1 - X0) >= 0) {
XDir = 1;
} else {
XDir = -1;
DeltaX = -DeltaX;
}
if ((DeltaY = Y1 - Y0) == 0) {
while (DeltaX-- != 0) {
X0 += XDir;
DrawPixel(pDC,X0, Y0, BaseColor);
}
return;
}
if (DeltaX == 0) {
do {
Y0++;
DrawPixel(pDC,X0, Y0, BaseColor);
} while (--DeltaY != 0);
return;
}
if (DeltaX == DeltaY) {
do {
X0 += XDir;
Y0++;
DrawPixel(pDC,X0, Y0, BaseColor);
} while (--DeltaY != 0);
return;
}
ErrorAcc = 0;
IntensityShift = 16 - IntensityBits;
WeightingComplementMask = NumLevels - 1;
if (DeltaY > DeltaX) {
ErrorAdj = ((unsigned long) DeltaX << 16) / (unsigned long) DeltaY;
while (--DeltaY) {
ErrorAccTemp = ErrorAcc;
ErrorAcc += ErrorAdj;
if (ErrorAcc <= ErrorAccTemp) {
X0 += XDir;
}
Y0++;
Weighting = ErrorAcc >> IntensityShift;
DrawPixel(pDC,X0, Y0, BaseColor + Weighting);
DrawPixel(pDC,X0 + XDir, Y0,
BaseColor + (Weighting ^ WeightingComplementMask));
}
DrawPixel(pDC,X1, Y1, BaseColor);
return;
}
ErrorAdj = ((unsigned long) DeltaY << 16) / (unsigned long) DeltaX;
while (--DeltaX) {
ErrorAccTemp = ErrorAcc;
ErrorAcc += ErrorAdj;
if (ErrorAcc <= ErrorAccTemp) {
Y0++;
}
X0 += XDir;
Weighting = ErrorAcc >> IntensityShift;
DrawPixel(pDC,X0, Y0, BaseColor + Weighting);
DrawPixel(pDC,X0, Y0 + 1,
BaseColor + (Weighting ^ WeightingComplementMask));
}
DrawPixel(pDC,X1, Y1, BaseColor);
}
Colored Version of WuLine
I hope this will come in handy for some of your applications.
Good reference materials:
Eien posted a colored version of the algorithm that I had planned as the next installment, for simplicity. :) Thank you, Eien!
Updates
The entire code for this has been hosted at Google Code to enable Open Source development. Please feel free to join that development group and contribute to it. At the moment, the FLTK project is using this work.
Your comments and suggestions are always welcome. Just post here.
History
- 8th March, 2006 -- Original version posted
- 10th March, 2006 -- Article moved
- 6th November, 2007 -- Article contents updated