Winter Visualization for Windows Media Player in C++
Ever wondered what's behind a visualization? This is your opportunity to learn something new because there are only a couple of Windows Media Player Visualizations that deliver source code. You'll learn to build a new visualization with Visual C++ 2005 Express (you won't even require a Standard Edition)! Winter is here... Snow is here... we'll render Koch Snowflake Fractals that depend on the waveform of the playing song with the Windows Graphics Device Interface. This article will also change the way you ever looked at a visualization. Enjoy!
Paul-Valentin Borza - http://www.borza.ro
Difficulty: Intermediate
Cost: Free
Time required: 1-3 hours
Software: Visual C++ 2005 Express, Windows Server 2003 R2 Platform SDK, Windows Software Development Kit for Windows Vista
Hardware: None
Download Source: Winter Visualization Source Code
Download Installer: WinterVisualizationSetup
Video Preview
Video: Winter Visualization For Windows Media Player
How do we build a visualization?
Please install (don't change the default installation directory):
We need to make Visual C++ 2005 Express work with visualizations:
- Navigate to 'C:\Program Files\Microsoft SDKs\Windows\v6.0\Samples\Multimedia\WMP_11\Wizards\VSNET';
- Copy 'wmpwiz2005.vsz', 'wmpwiz.vsdir' and 'wmpwiz.ico';
- Navigate to 'C:\Program Files\Microsoft Visual Studio 8\VC\Express\VCProjects';
- Paste 'wmpwiz2005.vsz', 'wmpwiz.vsdir' and 'wmpwiz.ico';
- Rename 'wmpwiz2005.vsz' to 'wmpwiz.vsz' (you're in 'C:\Program Files\Microsoft Visual Studio 8\VC\Express\VCProjects');
- Edit 'wmpwiz.vsz' using Notepad and change the line 'Param="ABSOLUTE_PATH = <path to wmpwiz directory goes here>"' with 'Param="ABSOLUTE_PATH = C:\Program Files\Microsoft SDKs\Windows\v6.0\Samples\Multimedia\WMP_11\Wizards\VSNET"';
- The Windows Media Player Plug-in Wizard is now installed. Launch Visual C++ 2005 Express.
- Click on 'File' > 'New' > 'Project...';
- In 'Project types:' select 'Visual C++';
- In 'Templates:' select 'Windows Media Player Plug-in Wizard';
- Change 'Name:' from 'wmpplugin1' to 'MyWinterVisualization' and click 'OK';
- The Windows Media Player Plug-in Wizard appears;
- On the 'Welcome to the Windows Media Player Plug-in Wizard' select 'Visualization' and click 'Next';
- On the 'Visualization Plug-in' change 'Friendly Name:' from 'MyWinterVisualization Plugin' to 'Coding4Fun Winter Visualization' and 'Description:' from 'Description of MyWinterVisualization plugin' to 'Description of Coding4Fun Winter Visualization'; don't check 'Property Page' or 'Listen to events';
- Click 'Next'. The wizard will create a new visualization for you; you're almost done!
You'll notice that the created project won't compile because the 'atlbase.h', 'wmpplug.h', 'effects.h' and 'winres.h' can't be opened; to overcome:
- Click on 'Project' > 'Properties';
- Expand 'Configuration Properties' > 'C/C++' > 'General';
- In 'Additional Include Directories' add '"C:\Program Files\Microsoft SDKs\Windows\v6.0\Include"' and click 'OK';
- Expand 'Configuration Properties' > 'Linker' > 'Input';
- In 'Additional Dependencies' add 'msimg32.lib' (we'll need this to give smoothness to the visualization);
- Click on 'Tools' > 'Options...';
- Expand 'Projects and Solutions' > 'VC++ Directories';
- In 'Platform:' select 'Win32' and in 'Show directories for:' select 'Include files';
- Add '"C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Include\atl"' and '"C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Include\mfc"';
One last trick... Edit 'C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Include\atl\atlbase.h' using Notepad. Find the following text (begins on line 287):
287 PVOID __stdcall __AllocStdCallThunk(VOID);
288 VOID __stdcall __FreeStdCallThunk(PVOID);
289
290 #define AllocStdCallThunk() __AllocStdCallThunk()
291 #define FreeStdCallThunk(p) __FreeStdCallThunk(p)
292
293 #pragma comment(lib, "atlthunk.lib")
Comment line 287 to 293 and add 'AllocStdCallThunk' and 'FreeStdCallThunk' to look like:
287 /*
288 PVOID __stdcall __AllocStdCallThunk(VOID);
289 VOID __stdcall __FreeStdCallThunk(PVOID);
290
291 #define AllocStdCallThunk() __AllocStdCallThunk()
292 #define FreeStdCallThunk(p) __FreeStdCallThunk(p)
293
294 #pragma comment(lib, "atlthunk.lib")
295 */
296
297 #define AllocStdCallThunk() HeapAlloc(GetProcessHeap(), 0, sizeof(_stdcallthunk))
298 #define FreeStdCallThunk(p) HeapFree(GetProcessHeap(), 0, p)
You should now be able to compile a visualization. Compile your visualization and launch Windows Media Player.
- Play a song; you can try My Fair Lady by David Byrne over ccMixter (this is the melody from the video preview);
- Click on 'Now Playing';
- Right-click inside Windows Media Player and click 'Coding4Fun Winter Visualization' > 'Coding4Fun Winter Visualization Bars' or 'Coding4Fun Winter Visualization Wave';
- Please take a good look at the generated visualization. Do you see the flicker (the flashing effect that you wouldn't want to see)? Don't worry, we'll make the visualization flicker-free in a few minutes.
How do we debug a visualization?
We know how to build and try a visualization; however, how do we debug one? Simple - we attach to the 'wmplayer.exe' process.
- Click on 'Project' > 'Properties';
- Expand 'Configuration Properties' > 'Debugging';
- In 'Command' enter 'C:\Program Files\Windows Media Player\wmplayer.exe';
- Change 'Attach' from 'No' to 'Yes';
- Click 'OK';
- Open 'MyWinterVisualization.cpp' and locate 'CMyWinterVisualization::Render';
- Add a breakpoint at line '60' in 'MyWinterVisualization.cpp';
- Build Solution and launch Windows Media Player;
- Start Debugging and select your visualization in Windows Media Player; the visualization should break in Visual Studio.
The Winter Visualization
From now on, we'll take a look at the source code for the Winter Visualization. A visualization can have one or more presets (Bars and Wave for example). I've decided to go with two presents for the Winter Visualization and named them: Snow Flakes (draws blue snowflakes) and Snow Flames (draws red snowflakes).
Configuration
Open 'WinterVisualization.h' and check the configuration values:
18 #define EQ_ANGLE -1.0471975511965977461542144610932f // - PI / 3
19 #define SNOW_MIN_SIZE 23 // in pixels
20 #define SNOW_MAX_SIZE 337 // in pixels
21 #define SNOW_STEP_SIZE 4 // in pixels
22 #define SNOW_COUNT 13 // number of snowflakes
23 #define SNOW_FLAKE_COLOR_COUNT 5 // number of blue colors
24 #define SNOW_FLAME_COLOR_COUNT 5 // number of red colors
25 #define SNOW_ALPHA 237 // alpha value
26 #define KOCH_IT 5 // number of iterations for Koch
Don't modify EQ_ANGLE as it's needed to make equilateral triangles for the Koch Snowflake Fractal. If you change SNOW_FLAKE_COLOR_COUNT or SNOW_FLAME_COLOR_COUNT, modify the appropriate lines in CWinterVisualization::FinalConstruct. The visualization involves a lot of computations and changing any configuration value could potentially slow it down. However, you're more than welcome to try anything... For example, increasing KOCH_IT to 6 will make the visualization run slower, but the snowflakes will have a more defined border. Try playing with SNOW_ALPHA (value should be between 0 and 255).
Initialization
Open 'WinterVisualization.cpp':
37 HRESULT CWinterVisualization::FinalConstruct()
38 {
39 tsPrev = 0; // previous time stamp
40 rcPrev.left = 0; // previous drawing surface
41 rcPrev.top = 0; // previous drawing surface
42 rcPrev.right = 0; // previous drawing surface
43 rcPrev.bottom = 0; // previous drawing surface
44
45 hdcPrev = 0; // previous handler to device context
46 hNewBitmapPrev = 0; // previous drawing bitmap
47
48 hdcMem = 0; // memory handler to device context
49 hNewBitmapMem = 0; // memory drawing bitmap
50
51 /* Create Elapsed Font */
52 LOGFONT lfElapsed;
53 lfElapsed.lfHeight = 23;
54 lfElapsed.lfWidth = 0;
55 lfElapsed.lfEscapement = 0;
56 lfElapsed.lfOrientation = 0;
57 lfElapsed.lfWeight = FW_BOLD;
58 lfElapsed.lfItalic = FALSE;
59 lfElapsed.lfUnderline = FALSE;
60 lfElapsed.lfStrikeOut = FALSE;
61 lfElapsed.lfCharSet = DEFAULT_CHARSET;
62 lfElapsed.lfOutPrecision = OUT_DEFAULT_PRECIS;
63 lfElapsed.lfClipPrecision = CLIP_DEFAULT_PRECIS;
64 lfElapsed.lfQuality = ANTIALIASED_QUALITY;
65 lfElapsed.lfPitchAndFamily = FF_MODERN;
66 _tcscpy_s(lfElapsed.lfFaceName, TEXT("\0"));
67 hfElapsed = CreateFontIndirect(&lfElapsed);
68
69 /* Create Plus Font */
70 LOGFONT lfPlus;
71 lfPlus.lfHeight = 9;
72 lfPlus.lfWidth = 0;
73 lfPlus.lfEscapement = 0;
74 lfPlus.lfOrientation = 0;
75 lfPlus.lfWeight = FW_BOLD;
76 lfPlus.lfItalic = FALSE;
77 lfPlus.lfUnderline = FALSE;
78 lfPlus.lfStrikeOut = FALSE;
79 lfPlus.lfCharSet = DEFAULT_CHARSET;
80 lfPlus.lfOutPrecision = OUT_DEFAULT_PRECIS;
81 lfPlus.lfClipPrecision = CLIP_DEFAULT_PRECIS;
82 lfPlus.lfQuality = ANTIALIASED_QUALITY;
83 lfPlus.lfPitchAndFamily = FF_MODERN;
84 _tcscpy_s(lfPlus.lfFaceName, TEXT("\0"));
85 hfPlus = CreateFontIndirect(&lfPlus);
86
87 /* Flake Colors */
88 hpSnowColor[0] = CreatePen(PS_SOLID, 1, RGB(99, 184, 255)); // #63B8FF
89 hpSnowColor[1] = CreatePen(PS_SOLID, 1, RGB(92, 172, 238)); // #5CACEE
90 hpSnowColor[2] = CreatePen(PS_SOLID, 1, RGB(30, 144, 255)); // #1E90FF
91 hpSnowColor[3] = CreatePen(PS_SOLID, 1, RGB(28, 134, 238)); // #1C86EE
92 hpSnowColor[4] = CreatePen(PS_SOLID, 1, RGB(16, 78, 139)); // #104E8B
93 /* Flame Colors */
94 hpSnowColor[5] = CreatePen(PS_SOLID, 1, RGB(220, 20, 60)); // #DC143C
95 hpSnowColor[6] = CreatePen(PS_SOLID, 1, RGB(255, 48, 48)); // #FF3030
96 hpSnowColor[7] = CreatePen(PS_SOLID, 1, RGB(238, 44, 44)); // #EE2C2C
97 hpSnowColor[8] = CreatePen(PS_SOLID, 1, RGB(205, 38, 38)); // #CD2626
98 hpSnowColor[9] = CreatePen(PS_SOLID, 1, RGB(139, 26, 26)); // #8B1A1A
99
100 hBrushBack = CreateSolidBrush(RGB(0, 0, 0));
101
102 iKochCount = (SNOW_MAX_SIZE - SNOW_MIN_SIZE) / SNOW_STEP_SIZE;
103 CreateSnow(); // populate pKoch
104
105 // Make snowflakes re-initialize in DrawSnow
106 for (INT i = 0; i < SNOW_COUNT; ++i)
107 iSnowSize[i] = iKochCount - 1;
108
109 return S_OK;
110 }
The surface size, the device contexts, the fonts, the pens and the brushes are initialized (the variables are declared in 'WinterVisualization.h'). Notice that I'm also precomputing the Koch Snowflake Fractal because it doesn't make sense to compute it each time a snowflake is drawn; see CWinterVisualization::CreateSnow for more details.
Don't forget that we're in the native C++ world and we have to release memory! The article won't cover the memory deallocation, but you can find it inside CWinterVisualization::FinalRelease.
Rendering the Elapsed Time
CWinterVisualization::DrawText renders the elapsed time in the top-right corner; use this method when you want to see how frequent your CWinterVisualization::Render gets called by Windows Media Player.
414 void CWinterVisualization::DrawText(TimedLevel * pLevels, HDC hdcMem, RECT rcMem)
415 {
416 // don't display elapsed text when screen is too small
417 if (rcMem.bottom < 240)
418 return;
419
420 HFONT hfOld = static_cast<HFONT>(SelectObject(hdcMem, hfElapsed));
421
422 SetBkMode(hdcMem, TRANSPARENT);
423 SetTextAlign(hdcMem, TA_RIGHT | TA_TOP);
424 SetTextColor(hdcMem, RGB(255, 255, 255));
425
426 /* Convert time stamp to string */
427 TCHAR tcElapsed[32];
428 if (_i64tot_s(pLevels->timeStamp, tcElapsed, _countof(tcElapsed), 10) == 0)
429 TextOut(hdcMem, rcMem.right, rcMem.top, tcElapsed, static_cast<int>(_tcslen(tcElapsed)));
430
431 SelectObject(hdcMem, hfOld);
432 }
pLevels->timeStamp is measured in 100 nanoseconds and indicates the elapsed time inside the playing song. Be aware that the 'SelectObject' method returns the previous/old selected object in the device context; you're advised to select the old object back in the device context after you've finished working with the new one.
Rendering the Wave
If you watched the video preview, you've seen that there is a wave in the middle of the screen. The CWinterVisualization::DrawPlus method draws plusses ('+') to build the wave (drawing plusses instead of connected lines gives a better effect).
382 void CWinterVisualization::DrawPlus(TimedLevel * pLevels, HDC hdcMem, RECT rcMem)
383 {
384 // don't display plus line when screen is too small
385 if (rcMem.right < 240)
386 return;
387
388 HFONT hfOld = static_cast<HFONT>(SelectObject(hdcMem, hfPlus));
389
390 SetBkMode(hdcMem, TRANSPARENT);
391 SetTextAlign(hdcMem, TA_RIGHT | TA_TOP);
392 switch (m_nPreset)
393 {
394 case PRESET_FLAKE:
395 SetTextColor(hdcMem, RGB(176, 226, 255));
396 break;
397 case PRESET_FLAME:
398 SetTextColor(hdcMem, RGB(255, 64, 64));
399 break;
400 }
401
402 TCHAR tcPlus[] = TEXT("+");
403 INT iPlusLen = static_cast<INT>(_tcslen(tcPlus));
404 for (INT i = 0; i < SA_BUFFER_SIZE; ++i)
405 {
406 INT x = static_cast<INT>(rcMem.right * i / 1024.0f);
407 INT y = static_cast<INT>(rcMem.bottom * pLevels->waveform[0][i] / 256.0f);
408 TextOut(hdcMem, x, y, tcPlus, iPlusLen);
409 }
410
411 SelectObject(hdcMem, hfOld);
412 }
This method depends on the currently selected preset and changes the color of the wave: blue for Flakes and red for Flames. We'll be rendering only the first channel (mono) waveform pLevels->waveform[0]. If you decide to use both channels (stereo), you'll have to use pLevels->waveform[0] and pLevels->waveform[1]; if the song is mono, then the second array is undefined (take care)! The method scales the values to the canvas size and draws the linked plusses.
Creating a Koch Snowflake
CWinterVisualization::CreateSnow computes the Koch Snowflake as described on the MathWorld Web Site. We construct an equilateral triangle with 3 vertices and start iterating: at the first iteration we have a polygon (which starts to resemble with a snowflake) with 12 vertices; at the second iteration we have a better snowflake/polygon with 48 vertices... at the fifth iteration we have a snowflake with 3072 vertices.
272 void CWinterVisualization::CreateSnow()
273 {
274 /* Create Koch */
275 pKoch = new POINT * [iKochCount];
276
277 /* Frames */
278 for (INT w = 0; w < iKochCount; ++w)
279 {
280 /* Initialize Koch Fractal Snowflake with the equilateral triangle */
281 INT iVertexNowCount = 3;
282 POINT * pVertexNow = new POINT[iVertexNowCount];
283
284 INT iSize = (SNOW_MIN_SIZE + w * SNOW_STEP_SIZE) / 2;
285 pVertexNow[0].x = - iSize; pVertexNow[0].y = 0; // A
286 pVertexNow[1].x = + iSize; pVertexNow[1].y = 23; // B
287 pVertexNow[2] = MakeEqTriangle(pVertexNow[0], pVertexNow[1]); // C
288
289 /* http://mathworld.wolfram.com/KochSnowflake.html */
290 for (INT q = 1; q < KOCH_IT + 1; ++q)
291 {
292 INT iVertexNewCount = iVertexNowCount * 4;
293 POINT * pVertexNew = new POINT[iVertexNewCount];
294
295 for (INT i = 0; i < iVertexNowCount; ++i)
296 {
297 INT j = (i + 1) % iVertexNowCount;
298
299 POINT pA = pVertexNow[i];
300 POINT pE = pVertexNow[j];
301
302 LONG lWidth = pE.x - pA.x;
303 LONG lHeight = pE.y - pA.y;
304
305 POINT pB;
306 pB.x = pA.x + lWidth / 3;
307 pB.y = pA.y + lHeight / 3;
308 POINT pD;
309 pD.x = pA.x + lWidth * 2 / 3;
310 pD.y = pA.y + lHeight * 2 / 3;
311 POINT pC = MakeEqTriangle(pD, pB); // equilateral triangle for the inner third
312
313 INT k = i * 4;
314 pVertexNew[k + 0] = pA;
315 pVertexNew[k + 1] = pB;
316 pVertexNew[k + 2] = pC;
317 pVertexNew[k + 3] = pD;
318 }
319 // move to the next Koch iteration
320 delete[] pVertexNow;
321 pVertexNow = pVertexNew;
322 iVertexNowCount = iVertexNewCount;
323 }
324 /* Done */
325 pKoch[w] = pVertexNow;
326 }
327 }
Rendering the Snowflakes
There are 13 (this is the default value) snowflakes that need to be rendered on the device context. When a snowflake reaches its maximum size, it gets reinitialized: its size depends on the waveform, its color depends on the preset and it's randomly distributed across the surface. Each snowflake is translated and rotated to its target position (this is required because of the precomputation where we considered the snowflake to be around x=0 and y=0).
329 void CWinterVisualization::DrawSnow(TimedLevel * pLevels, HDC hdcMem, RECT rcMem)
330 {
331 for (INT i = 0; i < SNOW_COUNT; ++i)
332 {
333 /* Initialize snowflake when reached SNOW_MAX_SIZE */
334 if (++iSnowSize[i] == iKochCount)
335 {
336 // size depends on waveform
337 iSnowSize[i] = iKochCount * pLevels->waveform[0][SA_BUFFER_SIZE * i / SNOW_COUNT] / 256;
338 switch (m_nPreset)
339 {
340 case PRESET_FLAKE:
341 iSnowColor[i] = rand() % SNOW_FLAKE_COLOR_COUNT;
342 break;
343 case PRESET_FLAME:
344 iSnowColor[i] = SNOW_FLAKE_COLOR_COUNT + rand() % SNOW_FLAME_COLOR_COUNT;
345 break;
346 }
347 // distribute snowflakes on the screen
348 pSnowPosition[i].x = rcMem.right * i / SNOW_COUNT;
349 pSnowPosition[i].y = rand() % rcMem.bottom;
350 }
351
352 /* Prepare snowflake for drawing */
353 INT iVertexNowCount = static_cast<INT>(3 * pow(4.0f, KOCH_IT));
354 POINT * pVertexNow = new POINT[iVertexNowCount];
355
356 for (INT j = 0; j < iVertexNowCount; ++j)
357 {
358 /* Just translation */
359 //pVertexNow[j].x = pKoch[iSnowSize[i]][j].x + pSnowPosition[i].x;
360 //pVertexNow[j].y = pKoch[iSnowSize[i]][j].y + pSnowPosition[i].y;
361 /* Translation and rotation */
362 FLOAT fAngle = pLevels->waveform[0][SA_BUFFER_SIZE * i / SNOW_COUNT] * 3.1415926535897932384626433832795f / 256.0f;
363 FLOAT fAngleCos = cos(fAngle);
364 FLOAT fAngleSin = sin(fAngle);
365 pVertexNow[j].x = static_cast<LONG>(fAngleCos * pKoch[iSnowSize[i]][j].x - fAngleSin * pKoch[iSnowSize[i]][j].y + pSnowPosition[i].x);
366 pVertexNow[j].y = static_cast<LONG>(fAngleSin * pKoch[iSnowSize[i]][j].x + fAngleCos * pKoch[iSnowSize[i]][j].y + pSnowPosition[i].y);
367 }
368
369 /* Draw Koch Snowflake */
370 HPEN hpOld = static_cast<HPEN>(SelectObject(hdcMem, hpSnowColor[iSnowColor[i]]));
371
372 MoveToEx(hdcMem, pVertexNow[0].x, pVertexNow[0].y, 0);
373 PolylineTo(hdcMem, pVertexNow, iVertexNowCount);
374 LineTo(hdcMem, pVertexNow[0].x, pVertexNow[0].y);
375
376 SelectObject(hdcMem, hpOld);
377
378 delete[] pVertexNow;
379 }
380 }
PolylineTo is the fastest way to draw a polygon when you use a pen with width 1. The width of the pen has to be one!
Wrapping up: Blending and Rendering
So far, we've seen some methods that did something if someone called them. CWinterVisualization::Render calls those methods and in turn is called by Windows Media Player (which decides how frequent to call it). Notice the line 169 which allows this method to be called only 30 times per second. Because the size of the drawing surface can change (when you resize Windows Media Player), the device contexts are deleted and created again.
167 STDMETHODIMP CWinterVisualization::Render(TimedLevel *pLevels, HDC hdc, RECT *prc)
168 {
169 if (_abs64(pLevels->timeStamp - tsPrev) < 333333) // 10^7 is 1 second; use only 30fps
170 return S_OK;
171 tsPrev = pLevels->timeStamp; // keep time stamp
172
173 RECT rcMem = {0, 0, prc->right - prc->left, prc->bottom - prc->top}; // memory rectangle
174
175 /* Check whether drawing surface changed */
176 if ((rcMem.left != rcPrev.left) || (rcMem.top != rcPrev.top) ||
177 (rcMem.right != rcPrev.right) || (rcMem.bottom != rcPrev.bottom))
178 {
179 rcPrev = rcMem; // keep memory rectangle
180
181 /* Delete Prev */
182 if (hNewBitmapPrev)
183 {
184 SelectObject(hdcPrev, hOldBitmapPrev);
185 DeleteObject(hNewBitmapPrev);
186 }
187 hNewBitmapPrev = 0;
188 hOldBitmapPrev = 0;
189 if (hdcPrev)
190 DeleteDC(hdcPrev);
191 hdcPrev = 0;
192
193 /* Delete Mem */
194 if (hNewBitmapMem)
195 {
196 SelectObject(hdcMem, hOldBitmapMem);
197 DeleteObject(hNewBitmapMem);
198 }
199 hNewBitmapMem = 0;
200 hOldBitmapMem = 0;
201 if (hdcMem)
202 DeleteDC(hdcMem);
203 hdcMem = 0;
204
205 // Make snowflakes re-initialize in DrawSnow
206 for (INT i = 0; i < SNOW_COUNT; ++i)
207 iSnowSize[i] = iKochCount - 1;
208 }
209
210 /* Create Prev */
211 if (!hdcPrev)
212 hdcPrev = CreateCompatibleDC(hdc);
213 if (!hNewBitmapPrev)
214 {
215 hNewBitmapPrev = CreateCompatibleBitmap(hdc, rcMem.right, rcMem.bottom);
216 hOldBitmapPrev = static_cast<HBITMAP>(SelectObject(hdcPrev, hNewBitmapPrev));
217 }
218
219 /* Create Mem */
220 if (!hdcMem)
221 hdcMem = CreateCompatibleDC(hdc);
222 if (!hNewBitmapMem)
223 {
224 hNewBitmapMem = CreateCompatibleBitmap(hdc, rcMem.right, rcMem.bottom);
225 hOldBitmapMem = static_cast<HBITMAP>(SelectObject(hdcMem, hNewBitmapMem));
226 }
227
228 /* Draw background on Mem */
229 FillRect(hdcMem, &rcMem, hBrushBack);
230
231 /* Blend Prev with Mem */
232 BLENDFUNCTION bfPrev = {AC_SRC_OVER, 0, SNOW_ALPHA, AC_SRC_ALPHA};
233 AlphaBlend(hdcMem, rcMem.left, rcMem.top, rcMem.right, rcMem.bottom,
234 hdcPrev, rcMem.left, rcMem.top, rcMem.right, rcMem.bottom, bfPrev);
235
236 /* Draw Snow on Mem */
237 DrawSnow(pLevels, hdcMem, rcMem);
238 /* Draw Plus on Mem */
239 DrawPlus(pLevels, hdcMem, rcMem);
240
241 /* Copy Mem to Prev */
242 BitBlt(hdcPrev, rcMem.left, rcMem.top, rcMem.right, rcMem.bottom,
243 hdcMem, rcMem.left, rcMem.top, SRCCOPY);
244
245 /* Draw Text on Mem */
246 /* Uncomment the following line to draw elapsed text in the top-right corner */
247 DrawText(pLevels, hdcMem, rcMem);
248
249 /* Copy Mem to WMP */
250 BitBlt(hdc, prc->left, prc->top, prc->right - prc->left, prc->bottom - prc->top,
251 hdcMem, rcMem.left, rcMem.top, SRCCOPY);
252
253 return S_OK;
254 }
I haven't said anything about the flicker... how was that avoided? Take a good look at this method (especially at line 250): we weren't drawing directly on the default device context as the wizard-generated visualization did, instead we were drawing on a memory device context hdcMem which was copied to the default device context only when everything was ready. It's a simple solution and it avoids the flicker! Never draw directly to the hdc received as the argument!
What about the smooth effect? We needed the msimg32.lib, especially the AlphaBlend method which blends two device contexts together. The blend between hdcPrev and hdcMem lets the previous rendered scene to be drawn again with a darker shade, while the new scene is as vivid as it should be; hdcMem is saved to hdcPrev to keep it for the next rendering when it becomes the old scene. That's it!
Conclusion
Winter Visualization, Snow Flakes:

Winter Visualization, Snow Flames:

You've created a beautiful winter visualization... Watch the dancing snowflakes and download the Winter Visualization Source Code or download only the installer WinterVisualizationSetup.msi (share the Winter Visualization with your friends). I hope you enjoyed working with C++ and you like what you've learned today!
Thanks to the Microsoft Student Partners - Microsoft Academic Program Team Romania for support. Happy Holidays!
Reference
Bio
Paul-Valentin Borza is in its third year of study at the Babeş-Bolyai University of Cluj-Napoca, Romania. Driven by his passion for technology, he was invited in 2005 to be a Microsoft Student Partners member - Microsoft Academic Program at the Faculty of Mathematics and Computer Science. He can be reached through his web site at www.borza.ro or on the Windows Live Network with windowslive@borza.ro.