Microsoft® DirectX® Transform provides you with powerful tools for manipulating two-dimensional (2-D) image data. With DirectX Transform, you can perform operations on a single image (for example, the Blur or BasicImage transform) or combine two images to form a single output image (for example, the Iris or Wheel transform).
The WipeDlg sample application shows how to host the Wipe transform in C++. This transform requires two input images (referred to in the code as "Image A" and "Image B"). The transform animates Image A, gradually turning it into Image B by sweeping a blended region of both images from left to right that gradually overlays Image B onto Image A. The following animation shows how this looks at different stages of the transform.
The WipeDlg example code is located in the the DirectX Transform Software Development Kit (SDK) in the Dxmedia\Samples\Multimedia\Dtrans\C++\Wipe\WipeDlg folder. This folder contains all of the source code and resource files needed to build the WipeDlg.exe application, as well as a Microsoft Visual Studio® project file. Most of the code discussed in this article is located in WipeDlg.cpp. To run the example, you may need to register the Wipe.dll. To do this, run the Regsvr32.exe program located in your C:\Windows\System folder, passing to it the path to Wipe.dll.
Run WipeDlg.exe (located in the Dxmedia\Samples\Multimedia\Dtrans\Bin\X86\ folder). When you start the application, you are prompted to load Image A and Image B. You can load them from the File menu. After the images are loaded, you can use the Play buttons to either animate Image B into Image A, or Image A into Image B. This animation results from changing the values of the Wipe transform's Progress property. The Properties button enables you to change two properties of the Wipe transform: GradientSize and Duration.
The GradientSize controls the percentage of the total window size that comprises the transition region between the images, while the Duration controls the amount of time (in seconds) the animation process takes. A small GradientSize produces a drawn-curtain kind of transition from Image A to Image B, and a large GradientSize causes a subtle fade from Image A to Image B. You can also adjust the trackbar control to set intermediate values for the Progress property and to see frames of the animation process where parts of both images are visible.
This application uses the Wipe transform contained in the Wipe.dll sample. The GradientSize is a custom property defined only for this particular transform. Both the Progress and Duration properties are inherited from the IDXEffect interface, which is used by all transforms that support animation. If a transform supports animation, then IDXEffect will be listed as a supported interface on its Transform Reference page.
The following sections examine the specific parts of the source code for the application that hosts this transform.
As with all Microsoft Windows® applications, the program execution begins in the WinMain function. This is a standard Windows function responsible for opening the dialog window and processing the dialog's message loop. Creation of the dialog happens with this call.
// Create the main window. hWnd = CreateDialog(hInst, MAKEINTRESOURCE(IDD_WipeDlg), NULL, (DLGPROC)MainWndProc); if(!hWnd || !g_pSurfFact) goto failure;Notice that if the program is unable to create the window or unable to create an IDXSurfaceFactory interface needed for the transforms, the application quits (the following section explains how the surfaces are created by CreateDialog). If Dxtrans.dll and Wipe.dll are properly registered by Windows, and you have sufficient memory, this call should succeed.
The program enters a standard message loop, which branches to the MainWindowProc function when a message is received. The function is passed a handle to the dialog message and the switch(uMsg) statement determines which function of WipeDlg.cpp to call. The HandleInitDialog function is executed when the window is created. HandleSize is called when a user resizes a window, and HandleTrackBar is called when the trackbar control value changes. The HandleCommand function is another switch that handles image file loading, playing the transform, and changing transform properties.
The important function to examine is HandleDrawItem, which draws a frame of transformed image output to the screen. Those details are examined in the Performing the Transform section. Next, this article examines how to initialize the transform and the DXSurface objects needed by the transform.
When the message loop calls the CreateDialog function, it automatically calls the MainWndProc function, as well, with the message WM_INITDIALOG. This function, in turn, calls HandleInitDialog, which is shown in the following:
///////////////////////////////////////////////////////////////// BOOL HandleInitDialog(HWND hWnd, HWND hwndFocus, LPARAM lParam) ///////////////////////////////////////////////////////////////// { HRESULT hr = S_OK; // Store the dialog handle. g_hDlg = hWnd; // Initialize the Progress trackbar control. SendDlgItemMessage( hWnd, IDS_PCTCOMP, TBM_SETRANGE, TRUE, MAKELONGcontained( 0, 100 ) ); SendDlgItemMessage( hWnd, IDS_PCTCOMP, TBM_SETTICFREQ, 20, 0 );The function begins executing by storing the dialog handle to a global variable (defined in Globals.cpp) and initializing the Progress trackbar control to zero.
The next two statements use Component Object Model (COM) methods to create a DXTransformFactory object (Transform Factory).
CoInitialize(NULL); // Create the Transform Factory. hr = CoCreateInstance( CLSID_DXTransformFactory, NULL, CLSCTX_INPROC, IID_IDXTransformFactory, (void **)&g_pTransFact );The CoInitialize method is a standard method that initializes the COM library. The second statement creates the IDXTransformFactory interface by using its class identifier (CLSID) and interface identifier (IID), receiving a pointer to the requested interface and an HRESULT that indicates whether the creation was successful. For more details on the use of these COM methods, you should consult one of the standard texts on COM or the COM Reference in the Platform SDK.
Now you can use the Transform Factory to "manufacture" transforms. These transforms operate on objects called DXSurfaces, which store all the image data. DXSurfaces are created with the IDXSurfaceFactory interface, which is exposed when the Transform Factory is created.
To use the DXSurfaceFactory object (Surface Factory), you must use the Transform Factory to get a pointer to the Surface Factory it exposed, as shown in the following:
hr = g_pTransFact->QueryService( SID_SDXSurfaceFactory, IID_IDXSurfaceFactory, (void **)&g_pSurfFact );The QueryService method is a standard COM interface used to get a pointer to a service of an interface. You pass the QueryService method a service ID, indicating to the Transform Factory to use the Surface Factory query service. You also pass QueryService an interface ID to get a pointer to a Surface Factory interface. It returns an HRESULT value, indicating whether the interface exists. If it returns a value of S_OK, it also returns a pointer you can use to access the Surface Factory's methods and to create DXSurfaces. It isn't necessary to create the surfaces until the program loads the image files, but the Surface Factory will be ready to manufacture them when that happens.
The following statement uses the Transform Factory to create an instance of a Wipe transform.
hr = g_pTransFact->CreateTransform( NULL, 0, NULL, 0, NULL, NULL, CLSID_DXTWipe, IID_IDXTransform, (void **)&g_pWipeTrans );At this stage there are no input or output surfaces for the transform, so the first four parameters are either NULL or zero. The next NULL indicates that there is no COM property bag (a stored property set) for this interface. The fourth NULL indicates that no transform errors need to be logged. The first parameter passed to the Transform Factory is the CLSID of a transform, which in this case indicates to the Factory that a Wipe transform needs to be manufactured. Each transform has its own CLSID. The second parameter tells the Transform Factory that you want a pointer to an IDXTransform interface. If successful, that pointer is stored in g_pWipeTrans, and after the input and output surfaces are identified, you can start using this transform on DXSurfaces.
As a final step, you obtain a pointer to an IDXEffect interface, which will enable you to access the transform Progress and Duration properties.
hr = g_pWipeTrans->QueryInterface( IID_IDXEffect, ( void **)&g_pEffect );Because all COM interfaces inherit from the IUnknown interface, they all have a QueryInterface method. This method is used to determine whether the requested interface is supported by its object, and if so, to get a pointer to the interface. To use QueryInterface, you should pass it the IID_IDXEffect parameter. If the Wipe transform supports this interface, it returns an S_OK value and a pointer to the location of the IDXEffect interface.
After the dialog initialization routine is complete, control of the program is returned to the message loop. One of the possible messages the loop can receive is a WM_COMMAND, indicating that the user has selected one of the dialog commands. When it gets one of these messages, program control goes to HandleCommand. This function determines which dialog control was selected and calls the appropriate message handler. However, no image output can occur until the images have been loaded into memory. This is handled by the following block of code, which is located in Commdlg.cpp.
case IDM_IMAGEA: case IDM_IMAGEB: IDXSurface **ppSurface = NULL; TCHAR szError[256] = _T(""); if (id == IDM_IMAGEA) { ppSurface = &g_pInSurfA; _tcscpy(szError, _T("Couldn't read Image A\n")); } else { ppSurface = &g_pInSurfB; _tcscpy(szError, _T("Couldn't read Image B\n")); } InitializeFileOpenDlg( g_hDlg ); if (PopFileOpenDlg (g_hDlg, szFileName, szTitleName)) { if (!ReadImageFile (ppSurface, szFileName)) MessageBox( g_hDlg, szError, _T("Load Error"), MB_ICONERROR ); } // If we have both surfaces, force send a resize // to force Setup and Repaint. HandleSize( g_hDlg, 0, 0, 0 ); break; } return; }First, the function initializes a DXSurface pointer and file name string. The DXSurface pointer refers to either Image A or Image B, depending on which image is being loaded. The routine then opens a dialog box so the user can select an image for loading.
When the routine has a file name for the image, it calls the ReadImageFile function located in Commdlg.cpp.
BOOL ReadImageFile (IDXSurface **lplpDXSurf, PTSTR pstrFileName) { WCHAR pwcsBuffer[256] ; HRESULT hr; #ifndef UNICODE mbstowcs( pwcsBuffer, pstrFileName, strlen(pstrFileName) ); #endif // Load Input surfaces. hr = g_pSurfFact->LoadImage( pwcsBuffer, NULL, NULL, &DDPF_PMARGB32, IID_IDXSurface, (void**)lplpDXSurf ); if (FAILED(hr)) MessageBox(g_hDlg, _T("Couldn't load image!"), _T ("Error Loading"), MB_ICONERROR ); return SUCCEEDED( hr ); }
The previous routine uses the Surface Factory to create a DXSurface with the LoadImage method. The first parameter is the file name of the image to load. The next two parameters are used only if the source is a Microsoft DirectDraw® image, which is not the case in this example. The pixel format to use for this DXSurface is specified by the fourth parameter. In this example, the Surface Factory is set to store the image in a 32-bit, alpha-premultiplied PMARGB32 format. You can use any of 12 formats, and the Surface Factory will convert the image format to the one specified. The fifth parameter specifies that the Surface Factory should return a pointer to an IDXSurface interface, and the last parameter is the location where the pointer should be stored.
The last step of the image loading process involves defining the input and output surfaces for the Wipe transform. This is initiated by a routine in the HandleSize function.
// Create output surface. SAFE_RELEASE(g_pOutSurf); CDXDBnds bnds; bnds.SetXYSize(DCWidth, DCHeight); hr = g_pSurfFact->CreateSurface(NULL, NULL, &DDPF_PMARGB32, &bnds, 0, NULL, IID_IDXSurface, (void**)&g_pOutSurf ); if (g_pInSurfA && g_pInSurfB) { if( SUCCEEDED( hr ) ) { IUnknown* In[2]; IUnknown* Out[1]; In[0] = g_pInSurfA; In[1] = g_pInSurfB; Out[0]= g_pOutSurf; hr = g_pWipeTrans->Setup( In, 2, Out, 1, 0 ); } }
If Image A and Image B have been loaded, the routine creates an output surface based on the size of the dialog window. The routine then initializes the arrays of input and output surface pointers to be used by the transform. For the Wipe transform, there are two inputs and one output. Finally, the Setup method is called with the number of inputs and outputs, and zero for an unused parameter. After this routine has run, the Wipe transform has pointers to its image inputs and a location to store its output.
Each transform that supports the IDXEffect interface has two properties that are used to produce an effect: Progress and Duration. The Progress property is a number from zero through one that specifies how much of the transform has been completed. In the case of the Wipe transform, setting Progress to zero shows only Image A, while setting it to 1 shows only Image B. Setting it to 0.5 shows some of Image B on the left, some of Image A on the right, and a blurry mixture of both images in the middle. This effect of setting the Progress value is specific to the Wipe transform. Changing this value on a different transform will have a different effect on the output. The Duration variable stores the length of time (in seconds) it should take for the transform to occur.
Each transform can support a custom interface that gives the transform properties that are specific to the kind of effect it is producing. For the Wipe transform, one custom property is the GradientSize. It specifies the width of the transition region between Image A and Image B as a fraction of the total window width. Setting a GradientSize of 0.01 produces a sharp transition region that sweeps across the screen, as if Image A were being peeled away to show Image B. Setting this value to 2.0 produces a gradient wider than the window, with Image A gradually fading to become Image B. Another property specific to transform type is the WipeStyle, which tells the gradient region to sweep left to right, or top to bottom. These properties can be set directly in code or changed through a dialog box created by the transform. The WipeDlg function uses the latter option in its HandleProperties function.
The Transform Reference section provides information about the custom properties of each transform. You don't have to set these properties, though. Each transform is created with reasonable default values for each property.
The following section shows how the transform is used in C++. A transform is started when the user clicks either of the Play buttons or adjusts the Progress trackbar control in the dialog box. In the following example, let's assume the user adjusts the trackbar control to somewhere in the middle of its range. The message handler in MainWndProc receives a WM_HSCROLL message and calls the HandleTrackBar function.
///////////////////////////////////////////////////////////////// void HandleTrackBar( HWND hwnd, HWND hctl, UINT code, int pos) ///////////////////////////////////////////////////////////////// { int nPosition = pos; TCHAR szString[256]; if ( (code == SB_THUMBPOSITION) && !CheckImages() ) return; nPosition = SendMessage( hctl, TBM_GETPOS, 0, 0 ); _stprintf(szString, _T("Progress: %u%%"), nPosition); SetDlgItemText( hwnd, IDT_PCTCOMP, szString); g_pEffect->put_Progress( (float)(nPosition/100.0) ); HandleDrawItem( NULL, NULL ); return; }The first statement checks that the two images have been loaded, and if not, returns from the function. Otherwise, the statement uses the SendMessage function to get the current trackbar control position and writes the value as text to the dialog window. The next call uses the transform IDXEffect interface created earlier to set the Progress of the transform. Because the Progress property is a value from zero to one, and Progress ranges from zero to 100, it is converted and passed to the put_Progress method.
The program must now redraw the screen based on the new value for the Progress property. WipeDlg has a separate function for doing this, called HandleDrawItem. This is an important function, which is explained in steps after the following sample code.
///////////////////////////////////////////////////////////////// void HandleDrawItem( HWND hwnd, const DRAWITEMSTRUCT *lpDrawItem ) ///////////////////////////////////////////////////////////////// // // Gets a Display Context from the owner draw button // on the dialog, then executes a frame of the transform // and blits the result over the owner draw button. // // The arguments are ignored. { HDC hdc = NULL; HRESULT hr = S_OK; HDC hdcSurf = NULL; IDirectDrawSurface *pDDSurf = NULL; HWND hOwnerDraw = NULL; DWORD DCWidth, DCHeight; RECT rect; // Get the handle and size of the owner draw window. hOwnerDraw = GetDlgItem( g_hDlg, IDB_OWNERDRAW ); GetClientRect( hOwnerDraw, &rect ); DCWidth = rect.right; DCHeight = rect.bottom; // Get the Display Context as a place to blit. hdc = GetDC(hOwnerDraw); // Erase the output surface with a color. DXFillSurface( g_pOutSurf, 0xFFC0C0FF, false );
The function starts by defining some variables, including handles to the device context for image output and a DirectDrawSurface to store the transformed result. It is followed by the GetDlgItem function that gets a handle to the dialog box image window, and the GetClientRect function, which retrieves the window's size. This allows the function to draw an image of the correct size. The set up for the transform continues as the GetDC function returns the output device context and the DXFillSurface helper function fills the output DXSurface with a default color.
With both input surfaces defined, the output surface initialized, and the value of the Progress property set, the transform is ready to execute, as shown in the following code.
// Execute if we have our inputs. if (g_pInSurfA && g_pInSurfB) { // Execute the transform. DXVEC Placement = { DXBT_DISCRETE, 0 }; hr = g_pWipeTrans->Execute( NULL, NULL, &Placement ); }You must specify a position for the transformed image on the output surface, as well as the variable type used to store the position for each transform. Passing a DXBT_DISCRETE from the DXBNDTYPE enumeration specifies LONG variables for the position. The zero specifies that you want no special position offset for the output image.
The important method call in the above code is Execute. It takes the two input images and combines them to fill the output surface. The first parameter passed to the method is used for multithreaded transforms and is usually set to NULL. The second parameter specifies the portion of the output image to be blitted to the output surface. To show all of the output, this is set to NULL. The last parameter is the output image position.
If the transform is successful, it returns S_OK, and the program continues.
if( SUCCEEDED( hr ) ) { // Get the DirectDrawSurface from the output DXTransform // surface. hr = g_pOutSurf->GetDirectDrawSurface( IID_IDirectDrawSurface, (void**)&pDDSurf ); if( SUCCEEDED( hr ) ) { // Get the DC from the DirectDraw surface. hr = pDDSurf->GetDC( &hdcSurf ); } } if( SUCCEEDED( hr ) ) { // Blit the output surface to the DC on the screen. BOOL bStat = BitBlt( hdc, 0, 0, DCWidth, DCHeight, hdcSurf, 0, 0, SRCCOPY ); hr = pDDSurf->ReleaseDC( hdcSurf ); } // Clean up and get out of here. ReleaseDC( hOwnerDraw, hdc ); SAFE_RELEASE( pDDSurf ); return; }
The remainder of the function takes care of rendering the output image to the screen, which uses standard DirectDraw methods. To do this, you need a pointer to a DirectDrawSurface that represents the transformed image. All DXSurface objects have the GetDirectDrawSurface method, which does just this. The first parameter of the method specifies a pointer to a DirectDrawSurface, and the second parameter is a reference to the returned pointer. From there, it's a matter of getting a device context for the DirectDrawSurface and blitting the result to the window. For more information on how that is done, consult a DirectDraw reference.
The previous section showed how to draw one frame of a transform, based on the value of the Progress property. Sweeping the transform left to right is just a loop over Progress, calling HandleDrawItem to update the screen for each iteration. The new feature shown in the following code is the Duration property, which determines the length of time for the transform to complete.
///////////////////////////////////////////////////////////////// void HandlePlayForward( void ) ///////////////////////////////////////////////////////////////// { float Duration, Progress = 0; DWORD msStartTime = timeGetTime(); ULONG ulFrames = 0; if (CheckImages()) { // This duration can be set with the property page for // the effect. It defaults to .50. g_pEffect->get_Duration( &Duration ); do { // Set Progress. g_pEffect->put_Progress( Progress ); // Cause repaint. HandleDrawItem( NULL, NULL ); // Next Progress = (float)(( (timeGetTime() - msStartTime) / 1000. ) / Duration); ++ulFrames; } while( Progress <= 1. ); // Update the dialog with the frame rate. TCHAR szFrames[20]; _stprintf( szFrames, _T("Frames/Sec = %.2f"), ( double)ulFrames / Duration ); SetDlgItemText( g_hDlg, IDT_FRAMES_PER_SEC, szFrames ); } return; }
The function starts by recording the start time of the transform with the Windows timeGetTime function and by initializing the Progress property to zero. The transform duration is then obtained by using the get_Duration method of the IDXEffect interface. The function enters the do loop by updating the most recent value for the transform's Progress property with the put_Progress method. HandleDrawItem updates the screen, and then Progress is recalculated based on the elapsed time. When the elapsed time is greater than the Duration, the loop ends. When the transform completes, the frame rate is calculated and displayed, showing how quickly your computer rendered the images.
To sweep the transform in the other direction, the HandlePlayBackward function is used. It is much the same as HandlePlayForward, but with the loop over Progress moving down from one to zero instead of up from zero to one.
Much of the previous code dealt with standard function calls in a Windows application. However, the code for performing a sophisticated transform took only a few lines. Setting things up required calls to methods in the IDXTransformFactory, the IDXSurfaceFactory, IDXTransform, and IDXEffect interfaces. Performing the transform required setting the Progress value and calling Execute. DirectX Transform took care of all the details for combining the images.
This SDK contains other sample code that uses transforms in Windows applications. These applications are listed in the Transform C++ Samples section. If you are interested in how the Wipe transform combines images to produce its output, you can read Example: The Wipe Transform.
Top of Page
© 2000 Microsoft and/or its suppliers. All rights reserved. Terms of Use.