Winter Visualization for Windows Media Player in C++

Published 20 December 07 02:52 PM | Coding4Fun 

small 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

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:

  1. Navigate to 'C:\Program Files\Microsoft SDKs\Windows\v6.0\Samples\Multimedia\WMP_11\Wizards\VSNET';
  2. Copy 'wmpwiz2005.vsz', 'wmpwiz.vsdir' and 'wmpwiz.ico';
  3. Navigate to 'C:\Program Files\Microsoft Visual Studio 8\VC\Express\VCProjects';
  4. Paste 'wmpwiz2005.vsz', 'wmpwiz.vsdir' and 'wmpwiz.ico';
  5. Rename 'wmpwiz2005.vsz' to 'wmpwiz.vsz' (you're in 'C:\Program Files\Microsoft Visual Studio 8\VC\Express\VCProjects');
  6. 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"';
  7. The Windows Media Player Plug-in Wizard is now installed. Launch Visual C++ 2005 Express.
    1. Click on 'File' > 'New' > 'Project...';
    2. In 'Project types:' select 'Visual C++';
    3. In 'Templates:' select 'Windows Media Player Plug-in Wizard';
    4. Change 'Name:' from 'wmpplugin1' to 'MyWinterVisualization' and click 'OK';
    5. The Windows Media Player Plug-in Wizard appears;
    6. On the 'Welcome to the Windows Media Player Plug-in Wizard' select 'Visualization' and click 'Next';
    7. 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';
    8. 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:

  1. Click on 'Project' > 'Properties';
  2. Expand 'Configuration Properties' > 'C/C++' > 'General';
  3. In 'Additional Include Directories' add '"C:\Program Files\Microsoft SDKs\Windows\v6.0\Include"' and click 'OK';
  4. Expand 'Configuration Properties' > 'Linker' > 'Input';
  5. In 'Additional Dependencies' add 'msimg32.lib' (we'll need this to give smoothness to the visualization);
  6. Click on 'Tools' > 'Options...';
  7. Expand 'Projects and Solutions' > 'VC++ Directories';
  8. In 'Platform:' select 'Win32' and in 'Show directories for:' select 'Include files';
  9. 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.

  1. Play a song; you can try My Fair Lady by David Byrne over ccMixter (this is the melody from the video preview);
  2. Click on 'Now Playing';
  3. Right-click inside Windows Media Player and click 'Coding4Fun Winter Visualization' > 'Coding4Fun Winter Visualization Bars' or 'Coding4Fun Winter Visualization Wave';
  4. 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.

  1. Click on 'Project' > 'Properties';
  2. Expand 'Configuration Properties' > 'Debugging';
  3. In 'Command' enter 'C:\Program Files\Windows Media Player\wmplayer.exe';
  4. Change 'Attach' from 'No' to 'Yes';
  5. Click 'OK';
  6. Open 'MyWinterVisualization.cpp' and locate 'CMyWinterVisualization::Render';
  7. Add a breakpoint at line '60' in 'MyWinterVisualization.cpp';
  8. Build Solution and launch Windows Media Player;
  9. 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:
capture1

Winter Visualization, Snow Flames:
capture2

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

Filed under: ,

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# MSDN Blogs » Blog Archive » Winter Visualization for Windows Media Player in C++ said on December 29, 2007 1:36 AM:

PingBack from http://msdn.blogsforu.com/2007/12/20/winter-visualization-for-windows-media-player-in-c/

# Andrey said on April 2, 2008 5:51 PM:

Url http://www.borza.ro/demo/WinterVisualization/WinterVisualization.zip broken!

# Coding4Fun said on April 2, 2008 11:47 PM:

@Andrey:  Thanks for the heads up, I'm on it.

# Davon Muchett said on October 13, 2008 5:32 AM:

this is totally off the hook every one should have this i even hate it because i love it its davon yow!!!!!!!!!!!!!!!!!!!

# Davon Muchett said on October 13, 2008 1:51 PM:

this visualization is off the hook i believe this is the best visualization ever if i could just take the whole world of it i would.

# Richard Haaf said on November 9, 2008 3:27 PM:

Thank you Paul-Valentin,

A few weeks ago my development machine with Visual Studio .NET, Media Player 10 and Media Player 10 SDK died.  This was replaced with a new machine with Visual Studion 2005 and Media Player 11.  I've been trying for weeks to figure out how to get the Project wizard in the 2008 Windows SDK to work with Visual Studio 2005.  The Microsoft documentation doesn't give a clue how to do this.  Your explaination gave me all the info I needed to get it to work.  As an added plus, you described exactly what I've needed to get off-screen buffering to work.

Many Thanks!!

# Adriana said on December 10, 2008 4:48 PM:

Last year I was doing an engineering project for  my Science Fair Project. I wanted to create a Music Visualization and this tutorial saved my grade and project. Thank you!

# Martin said on February 8, 2009 7:48 AM:

It looked like fun project, but unfortunately Visual Studio could not load the Windows Media Player Plug-In Wizard.

Running Windows XP, Visual Studio C++ 2008 Express, SDK version 6.1. I have changed the path-adresses accordingly - that is equivalently to your description. Get the fail message on status bar at the bottom of the Studio: Creating Project ....  project creation failed. What can I have done wrong?

# Coding4Fun said on February 10, 2009 1:47 PM:

@Martin, looking into it.

# Paul said on February 11, 2009 12:03 PM:

@Martin,

Have you installed the 2003 R2 SDK also? Although it's similar with the SDK for Vista, you need to install that in order to make it work. However, those steps for changing paths are only required for the Express editions -- if you have a standard or higher version, there's no need to make those.

Do you have a more clearer error message than "creation failed"?

# Paul said on February 11, 2009 12:04 PM:

@Martin,

Have you installed the 2003 R2 SDK also? Although it's similar with the SDK for Vista, you need to install that in order to make it work. However, those steps for changing paths are only required for the Express editions -- if you have a standard or higher version, there's no need to make those.

Do you have a more clearer error message than "creation failed"?

# Martin said on February 24, 2009 6:04 AM:

I found 2003 R2 SDK, and it did not install, stating I already have got the latest version of the plug-in program. Same result as before.

And no, sorry, "creation failed" is the only message I get. It is a small, almost not notable message, at the bottom part of the Express window.

# Gautham G said on April 5, 2009 8:16 PM:

I installed the windows media player plug-in wizard. Got Windows SDK (newer version of platform SDK)... When I create a new project and select the wizard, it opens up an IE page, but it says it can't find the webpage i'm looking for.

# Alan Z said on April 30, 2009 7:10 PM:

well im having trouble, but not with the installation and the other things.

anyway when i finished writing the code for the Initialization part of the tutorial and i went 2 build the code i get the undeclared errors so then i go and add the #include "WinterVisualization.h" all the errors go away but then i get another error saying that it can't open the include file. so im wondering what i need 2 do that will hep me get past the problem.

So does anyone know how i can declare all the undeclared units or what ever you want to call them?

or can some one give me a link to a working code of this visualization, or even a screen shot or even email me the code.  

# Josue said on May 26, 2009 5:16 PM:

since I can create this for my  windows media player mobile .please help me

          josuejavr@hotmail.com

# Lachlan said on November 9, 2009 3:22 PM:

Url http://www.borza.ro/demo/WinterVisualization/WinterVisualization.zip broken! (again)

# Coding4Fun said on November 10, 2009 9:31 AM:

crud, ok, on it.

# Coding4Fun said on November 10, 2009 10:02 AM:

@Lachlan I emailed Paul, he is currently out of the country for two weeks.  If you email me (code4fun@microsoft.com), I'll email you when I get the files.  I'll also update the post with new links on our server.

# Coding4Fun said on November 19, 2009 9:08 PM:

Fixed!  Source now lives on the Coding4Fun server.  Sorry about that.  http://coding4fun.net/source/WinterVisualization.zip

Leave a Comment

(required) 
(optional)
(required) 

  
Enter Code Here: Required

Search

This Blog

Syndication

Page view tracker