This article walks you through the Wipe transform sample code. This transform uses custom helper functions and elements common to all dynamic-link library (.dll) files that use Microsoft® DirectX® Transform routines.
This article assumes you are familiar with Component Object Model (COM) and that you have run the WipeDlg.exe so that you are familiar with the appearance of the transform.
The transforms for WipeDlg.exe are contained in Wipe.dll. The Microsoft Visual Studio® project for building this .dll file is located in the Dxmedia\Samples\Multimedia\Dtrans\C++\Wipe\Wipedll folder. To see how Wipe.dll combines the two images to form the output, open the Wipe.dsp project file and look at the DxtWipe.cpp source file. The program follows three steps to produce the transform.
This article contains the following sections.
The Using 2-D Transforms article walks you through WipeDlg.exe, which uses a transform to gradually change one image into another. For WipeDlg.exe, WipeDlg.cpp takes care of loading and displaying the images, and DirectX Transform takes care of the detailed combination of the images. See the Transform Reference for information about other transforms included with this Software Development Kit (SDK) that you can add to your applications.
The DirectX Transform architecture enables you to develop your own ideas for transitions and effects and implement them in code in two ways. You can load the images as part of an application into DXSurface objects and manipulate them sample-by-sample to form a new image by using the IDXARGBReadPtr and IDXARGBReadWritePtr interfaces. Or you can place the algorithm that produces the effect into a separate transform that applications will be able to invoke by using COM methods. The latter way is preferred because it makes the transform separate from the application and allows it to be easily reused and distributed.
The Wipe transform divides the viewing area of the source surfaces into three areas.
The previous illustration shows a typical Wipe transform. Because the default transform sweeps from left to right, the boundary between the Image A area and the gradient is called the leading edge. Similarly, the boundary between Image B and the gradient is called the trailing edge. The positions of these two boundaries relative to the image frame determine how the samples in a given row will be combined to form a row in the output image. For samples from the left edge of the image to the trailing edge, there is no mixing; all samples are from Image B. From the leading edge to the right edge of the image, all the samples are from Image A. In the region from the trailing edge to the leading edge, samples from the two images are mixed to produce a smooth transition between the two.
The following code shows two variables that determine these positions, which are set by the calling application. The Progress property is a number from 0.0 to 1.0, indicating how much of the transition has completed. The m_GradPercentSize variable specifies what percentage of the total image width to use as the gradient region. Note that this percentage can be greater than 100 percent, making the gradient width larger than the entire image width. This also means that the leading and trailing edges are not necessarily inside the image boundaries.
_ComputeStartPoints is a helper function in DxtWipe.cpp that calculates the location of the leading and trailing edges, which are used to locate the AStart and GradStart positions. In addition, it calculates AWid and BWid, which are used later to get the correct number of samples from each image.
void CDXTWipe::_ComputeStartPoints(const CDXDBnds & bnds, ULONG & AWid, ULONG & AStart, ULONG & BWid, ULONG & BUnpackWid, ULONG & GradStart, ULONG & GradWid, ULONG & GradWgtStart) { AWid = 0, AStart = 0, BWid = 0, BUnpackWid = 0; GradStart = 0, GradWid = 0, GradWgtStart = 0; ULONG BndsWid = bnds.Width(); ULONG ulRange = m_InputSize.cx + m_GradientSize; long LeadEdgePos = (long)(ulRange * GetEffectProgress()); long TrailEdgePos = LeadEdgePos - m_GradientSize; if( LeadEdgePos >= bnds.Left() ) { if( LeadEdgePos <= bnds.Right() ) { if( TrailEdgePos < bnds.Left() ) { // Leading in-between, trailing out GradWid = m_GradientSize - ( bnds.Left() - TrailEdgePos ); GradWgtStart = bnds.Left() - TrailEdgePos; } else { // Leading and Trailing in between GradWid = m_GradientSize; BWid = TrailEdgePos - bnds.Left(); } GradStart = m_GradientSize - GradWid; AWid = BndsWid - GradWid - BWid; } else { if( TrailEdgePos < bnds.Left() ) { // Leading is past right, trailing is before left GradWid = BndsWid; GradWgtStart = bnds.Left() - TrailEdgePos; } else if( TrailEdgePos < bnds.Right() ) { // Leading is past right, trailing is in between GradWid = bnds.Right() - TrailEdgePos; BWid = BndsWid - GradWid; } else { // All B BWid = BndsWid; } } GradStart = BWid; BUnpackWid = BWid + GradWid; AStart = GradStart + GradWid; } else { // All A AWid = BndsWid; } }
As shown in the previous code, after all the variables are initialized, the routine locates the leading and trailing edges. Notice that the leading edge position is the product of the image width and the Progress. After initialization, each possible combination of edge locations is tested, with the width and positions of the gradient, the A region, and the B region set accordingly. In cases where the trailing edge is located to the left of the image boundary, the GradWgtStart variable records how much of the gradient region lies outside the image. Notice also that ulRange is equal to the sum of the full image width and the gradient regions. This means that the trailing edge of the gradient is at the right boundary of the image when Progress is equal to one, and only Image B is visible.
When the Setup method is called for this transform by the application, the transform interface calls the OnSetup function.
HRESULT CDXTWipe::OnSetup( DWORD dwFlags ) { // Compute the effect step resolution and weights. HRESULT hr; CDXDBnds InBounds(InputSurface(0), hr); if (SUCCEEDED(hr)) { InBounds.GetXYSize(m_InputSize); _UpdateStepRes(); hr = _UpdateGradWeights(); } return hr;
The function in the previous code starts when a CDXBnds object is created and initialized with the bounds of one of the input surfaces. Inside the .dll code, the input and output surface pointers are obtained with the InputSurface and OutputSurface methods of the CDXBaseNTo1 class. Because the input index of zero corresponds to Image A, this stores the bounds of Image A in the InBounds object. The m_InputSize data member of the Wipe transform object is initialized with this width, and two other functions are called. _UpdateStepRes calculates m_GradientSize based on the custom property member m_GradPercentSize, as shown in the following:
m_GradientSize = (long)(m_InputSize.cx * m_GradPercentSize);
The following code shows _UpdateGradWeights, which creates an array of gradient weights that step smoothly from 255 to zero. The size of the array is equal to the width of the gradient region. Elements of this array are used later as an alpha channel to mix different amounts of Image A and Image B samples.
HRESULT CDXTWipe::_UpdateGradWeights( void ) { HRESULT hr = S_OK; delete m_pGradientWeights; m_pGradientWeights = new ULONG[m_GradientSize]; if( !m_pGradientWeights ) { hr = E_OUTOFMEMORY; } else { float fWeight = 1.0, fInc = 1.f / m_GradientSize; for(long i = 0; i < m_GradientSize; ++i, fWeight -= fInc ) { m_pGradientWeights[i] = (ULONG)(fWeight * 255.); } } return hr;
When the application calls the IDXTransform::Execute method, the WorkProc function of DxtWipe.cpp is executed, and the two images are combined row-by-row into a single output image. The WI variable is a pointer to a CDXTWorkInfoNTo1 object passed to the function that contains the bounds of output surface and an HRESULT that indicates to the calling routine whether the transform was successful. The pbContinue parameter is used for multithreading, causing the .dll to exit if its thread is stopped.
HRESULT CDXTWipe::WorkProc( const CDXTWorkInfoNTo1& WI, BOOL* pbContinue ) { HRESULT hr = S_OK; // Get input sample access pointer for the requested region. // Note: Lock may fail due to a lost surface. CComPtr<IDXARGBReadPtr> pInA; hr = InputSurface( 0 )->LockSurface( &WI.DoBnds, m_ulLockTimeOut, DXLOCKF_READ, IID_IDXARGBReadPtr, (void**)&pInA, NULL ); if( FAILED( hr ) ) return hr; CComPtr<IDXARGBReadPtr> pInB; hr = InputSurface( 1 )->LockSurface( &WI.DoBnds, m_ulLockTimeOut, DXLOCKF_READ, IID_IDXARGBReadPtr, (void**)&pInB, NULL ); if( FAILED( hr ) ) return hr; // Put a write lock only on the region we are updating so // multiple threads don't conflict. // Note: Lock may fail due to a lost surface. CComPtr<IDXARGBReadWritePtr> pOut; hr = OutputSurface()->LockSurface( &WI.OutputBnds, m_ulLockTimeOut, DXLOCKF_READWRITE, IID_IDXARGBReadWritePtr, (void**)&pOut, NULL ); if( FAILED( hr ) ) return hr;
The preceding code begins by declaring COM pointer variables to DirectX Transform read and read/write interfaces. The input and output surfaces for the transform are not referenced directly, but with the InputSurface and OutputSurface member functions of the CDXBaseNTo1 class. Multiple surface inputs are usually stored in arrays and indexed as previously shown. The two input surfaces are locked with the IDXSurface::LockSurface method. Because the lock is made with the DXLOCKF_READ and IID_IDXARGBReadPtr parameters, the method returns read pointers to the two input surfaces. Then the output surface is locked for writing by specifying the DXLOCKF_READWRITE and IID_IDXARGBReadWritePtr parameters.
The following code continues the set up for the image combination.
// Allocate a working buffer ULONG DoBndsWid = WI.DoBnds.Width(); DXPMSAMPLE* pRowBuff = DXPMSAMPLE_Alloca( DoBndsWid ); // Allocate output buffer if needed DXPMSAMPLE *pOutBuff = NULL; if( OutputSampleFormat() != DXPF_PMARGB32 ) { pOutBuff = DXPMSAMPLE_Alloca( DoBndsWid ); } // Compute the width of each region. ULONG ulDoNumRows = WI.DoBnds.Height(); ULONG AWid, AStart, BWid, BUnpackWid, GradStart, GradWid, GradWgtStart; _ComputeStartPoints(WI.DoBnds, AWid, AStart, BWid, BUnpackWid, GradStart, GradWid, GradWgtStart); // Allocate working buffers for the gradient area. DXPMSAMPLE *pGradBuff = DXPMSAMPLE_Alloca( GradWid ); // // Set up the dither structure. // DXDITHERDESC dxdd; if (DoDither()) { dxdd.x = WI.OutputBnds.Left(); dxdd.y = WI.OutputBnds.Top(); dxdd.pSamples = pRowBuff; dxdd.cSamples = DoBndsWid; dxdd.DestSurfaceFmt = OutputSampleFormat(); }
To combine the samples from the two input images, the code requires a working buffer that can hold an entire row of image data. The pRowBuff array serves this purpose and is allocated with the DXPMSAMPLE_Alloca helper function. In cases where the transform uses the OverArrayAndMove method to do a composite onto the destination surface, the code needs to allocate a scratch buffer for the operation. This is needed only if the output surface's pixel format is not PMARGB32, so that is checked before allocation.
Next, the leading and trailing edge calculations are performed by _ComputeStartPoints, using the bounds of the output surface. The function returns all the parameters that are needed to unpack the image data, including the gradient width. Combining the data in the gradient region also requires a working buffer, which is allocated after the function call. Finally, if dithered output is required, a structure is initialized to enable it to be produced.
The code then enters the main loop where it reads image data from each of the regions into arrays and combines samples in the gradient region, as shown in the following code.
// === Main loop ================================================= for(ULONG OutY = 0; *pbContinue && (OutY < ulDoNumRows); ++OutY) { // Get leading solid B samples if( BUnpackWid ) { pInB->MoveToRow( OutY ); pInB->UnpackPremult( pRowBuff, BUnpackWid, FALSE ); }
If there is an Image B region that shows in the output, the routine moves to the current row in pInB and reads the samples into the working buffer. This is done with the IDXARGBReadPtr::UnpackPremult method, which extracts a number of samples in the native pixel format and converts them to PMARGB32 format, if needed. This saves the step of multiplying each color component by alpha before combining the samples. Notice also that because BUnpackWid = BWid + GradWid, the routine is unpacking samples all the way up to the leading edge.
The loop continues by calculating sample colors for the mixed area of the gradient region.
// Compute gradient transition area if( GradWid ) { pInA->MoveToXY( BWid, OutY ); pInA->UnpackPremult( pGradBuff, GradWid, FALSE ); for( ULONG i = 0; i < GradWid; ++i ) { ULONG Wgt = m_pGradientWeights[GradWgtStart+i]; pRowBuff[BWid+i] = DXScaleSample( pGradBuff[i], Wgt ^ 0xFF ) + DXScaleSample( pRowBuff[BWid+i], Wgt ); } }
If there is a gradient region, MoveToXY jumps back to the trailing edge position on the same row on Image A and reads in enough samples into the gradient buffer to fill the gradient region. In the loop over the gradient row that follows, the samples are combined based on the value of the gradient weight. Each sample is scaled by the helper function DXScaleSample, which takes a sample color and a weight from zero to 255 and returns the scaled color. Samples from Image B are scaled by Wgt, and samples from Image A are scaled by 1-Wgt, and the sum of the two represents the output.
// Get trailing solid A samples if( AWid ) { pInA->MoveToXY( AStart, OutY ); pInA->UnpackPremult( pRowBuff + AStart, AWid, FALSE ); }
If there is any region of Image A showing, it is read into pRowBuff, completing this row of the output image. All that remains is to write the completed row to the output surface.
// Get the output row pOut->MoveToRow( OutY ); if (DoDither()) { DXDitherArray(&dxdd); dxdd.y++; } if (DoOver()) { pOut->OverArrayAndMove(pOutBuff, pRowBuff, DoBndsWid); } else { pOut->PackPremultAndMove(pRowBuff, DoBndsWid); } } // End for return hr; }
First, the write pointer is moved to the correct row of the output surface. If the transform has specified that the output should be dithered, that is done by the DXDitherArray helper function. The method used to write the output row on the destination surface depends on whether the output row should be blended onto the surface. If so, the OverArrayAndMove method is used. This is the method that requires a scratch buffer to hold the blended output of pRowBuff and the destination surface, which was allocated earlier as pOutBuff. If the pRowBuff should replace the corresponding row on the destination surface, the PackPremultAndMove method performs the correct operation.
The loop is executed for each row of the two images, until the output surface is filled with the combined image.
This particular image transform used a number of custom helper functions to produce an image effect. There are a number of elements to this routine, however, that are common to all .dll files that use DirectX Transform routines.
Top of Page
© 2000 Microsoft and/or its suppliers. All rights reserved. Terms of Use.