www.pudn.com > Win32DirectX9.rar > didevimg.cpp
//----------------------------------------------------------------------------- // File: DIDevImg.cpp // // Desc: Implementation for CDIDevImage class, which encapsulates methods for // drawing device images, callout strings, and object highlights. // // Copyright( c ) 1998-2001 Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #include "DIDevImg.h" #include//----------------------------------------------------------------------------- // Name: CDIDevImage // Desc: Constructor //----------------------------------------------------------------------------- CDIDevImage::CDIDevImage() { m_bInitialized = FALSE; m_bCustomUI = FALSE; m_bInvalidUI = TRUE; m_ahImages = NULL; m_dwNumViews = 0; m_dwNumObjects = 0; m_apObject = NULL; m_dwWidthPref = 0; m_dwHeightPref = 0; m_dwScaleMethod = 0; m_BkColor = D3DCOLOR_ARGB(255, 0, 0, 0); m_hFont = NULL; } //----------------------------------------------------------------------------- // Name: ~CDIDevImage // Desc: Destructor //----------------------------------------------------------------------------- CDIDevImage::~CDIDevImage() { CleanUp(); } //----------------------------------------------------------------------------- // Name: Init // Desc: Responsible for initializing and preparing the CDIDevImage object for // rendering the device image. Init must be called before the other // public functions. // Args: pDIDevice - Pointer to a DirectInputDevice object for which the // configuration UI should be created. // Ret: DI_OK - Success; image found. // DI_IMAGENOTFOUND - Success; no image. Default UI created instead. // DIERR_INVALIDPARAM - Fail; invalid argument passed. //----------------------------------------------------------------------------- HRESULT CDIDevImage::Init( LPDIRECTINPUTDEVICE8 pDIDevice ) { HRESULT hr = DI_OK; // Sanity check if( NULL == pDIDevice ) return DIERR_INVALIDPARAM; // Always start clean CleanUp(); // retrieve the image info from DirectInput hr = LoadImageInfo( pDIDevice ); if( FAILED(hr) ) { // Image information may have partially loaded. Clean out whatever // is stored since we'll need to create a custom UI from scratch. CleanUp(); // For one reason or another, the image info for this device // could not be loaded. At this point, create a default UI. m_bCustomUI = true; hr = CreateCustomImageInfo( pDIDevice ); if( FAILED(hr) ) { SAFE_RELEASE(pDIDevice); CleanUp(); return hr; } } // Create the default callout font CreateFont(); // Made it through initialization. Set the initialized flag to allow the // other member functions to be called. m_bInitialized = true; // Both of these values indicate success, but the client may wish to know // whether an image was actually found for this device, or if we are just // producing a default UI. return m_bCustomUI ? DI_IMAGENOTFOUND : DI_OK; } //----------------------------------------------------------------------------- // Name: SetCalloutState // Desc: Sets the state for a specific callout // Args: dwObjId - Object ID of the requested callout. This corresponds to the // dwType value returned by EnumDeviceObjects. // dwCalloutState - New state of the callout, which may be zero or more // combinations of: // DIDICOS_HIGHLIGHTED - Overlay drawn. Callout drawn in high color. // DIDICOS_INVISIBLE - Overlay and callout string not drawn. // DIDICOS_TOOLTIP - Tooltip drawn if callout text is truncated. // Ret: DI_OK - Success. // DIERR_NOTINITIALIZED - Fail; Init() must be called first. // DIERR_OBJECTNOTFOUND - Fail; Given ID not found in list of objects //----------------------------------------------------------------------------- HRESULT CDIDevImage::SetCalloutState( DWORD dwObjId, DWORD dwCalloutState ) { // Verify initialization if( false == m_bInitialized ) return DIERR_NOTINITIALIZED; CDIDIObject* pObject = GetObject( dwObjId ); if( NULL == pObject ) return DIERR_OBJECTNOTFOUND; DWORD dwOldState = pObject->GetCalloutState(); pObject->SetCalloutState( dwCalloutState ); // This action might change the layout for the custom UI if( m_bCustomUI && ( DIDICOS_INVISIBLE & dwOldState || DIDICOS_INVISIBLE & dwCalloutState ) ) m_bInvalidUI = TRUE; return DI_OK; } //----------------------------------------------------------------------------- // Name: GetCalloutState // Desc: Returns the state of a specific callout // Args: dwObjId - Object ID of the requested callout. This corresponds to the // dwType value returned by EnumDeviceObjects. // pdwCalloutState - Pointer to a variable which will contain the current // callout value after a successful return. // Ret: DI_OK - Success. // DIERR_NOTINITIALIZED - Fail; Init() must be called first. // DIERR_OBJECTNOTFOUND - Fail; Given ID not found in list of objects //----------------------------------------------------------------------------- HRESULT CDIDevImage::GetCalloutState( DWORD dwObjId, LPDWORD pdwCalloutState ) { // Verify initialization if( false == m_bInitialized ) return DIERR_NOTINITIALIZED; CDIDIObject* pObject = GetObject( dwObjId ); if( NULL == pObject ) return DIERR_OBJECTNOTFOUND; *pdwCalloutState = pObject->GetCalloutState(); return DI_OK; } //----------------------------------------------------------------------------- // Name: SetCalloutColors // Desc: Sets the callout-unique colors to be used when painting a specific // callout // Args: dwObjId - Object ID of the requested callout. This corresponds to the // dwType value returned by EnumDeviceObjects. // crColorNormal - Foreground text color for callout strings in a normal // state. // crColorHigh - Foreground text color for callout strings in a // highlighted state. // Ret: DI_OK - Success. // DIERR_NOTINITIALIZED - Fail; Init() must be called first. // DIERR_OBJECTNOTFOUND - Fail; Given ID not found in list of objects //----------------------------------------------------------------------------- HRESULT CDIDevImage::SetCalloutColors( DWORD dwObjId, COLORREF crColorNormal, COLORREF crColorHigh ) { // Verify initialization if( false == m_bInitialized ) return DIERR_NOTINITIALIZED; CDIDIObject* pObject = GetObject( dwObjId ); if( NULL == pObject ) return DIERR_OBJECTNOTFOUND; pObject->SetCalloutColors( crColorNormal, crColorHigh ); return DI_OK; } //----------------------------------------------------------------------------- // Name: GetCalloutColors // Desc: Obtains the callout-unique colors used when painting a specific // callout // Args: dwObjId - Object ID of the requested callout. This corresponds to the // dwType value returned by EnumDeviceObjects. // pcrColorNormal - Pointer to a COLORREF variable which will contain // the normal callout color after a successful return. May be NULL. // pcrColorHigh - Pointer to a COLORREF variable which will contain // the highlight callout color after a successful return. May be NULL. // Ret: DI_OK - Success. // DIERR_NOTINITIALIZED - Fail; Init() must be called first. // DIERR_OBJECTNOTFOUND - Fail; Given ID not found in list of objects //----------------------------------------------------------------------------- HRESULT CDIDevImage::GetCalloutColors( DWORD dwObjId, LPCOLORREF pcrColorNormal, LPCOLORREF pcrColorHigh ) { // Verify initialization if( false == m_bInitialized ) return DIERR_NOTINITIALIZED; CDIDIObject* pObject = GetObject( dwObjId ); if( NULL == pObject ) return DIERR_OBJECTNOTFOUND; pObject->GetCalloutColors( pcrColorNormal, pcrColorHigh ); return DI_OK; } //----------------------------------------------------------------------------- // Name: SetCalloutText // Desc: Sets the text associated with the callout specified by an object ID // Args: dwObjId - Object ID of the requested callout. This corresponds to the // dwType value returned by EnumDeviceObjects. // strText - New callout text. // Ret: DI_OK - Success. // DIERR_INVALIDPARAM - Fail; Null pointer passed. // DIERR_NOTINITIALIZED - Fail; Init() must be called first. // DIERR_OBJECTNOTFOUND - Fail; Given ID not found in list of objects //----------------------------------------------------------------------------- HRESULT CDIDevImage::SetCalloutText( DWORD dwObjId, LPCTSTR strText ) { // Verify initialization if( false == m_bInitialized ) return DIERR_NOTINITIALIZED; if( NULL == strText ) return DIERR_INVALIDPARAM; CDIDIObject* pObject = GetObject( dwObjId ); if( NULL == pObject ) return DIERR_OBJECTNOTFOUND; pObject->SetCalloutText( strText ); // This action might change the layout for the custom UI if( m_bCustomUI ) m_bInvalidUI = TRUE; return DI_OK; } //----------------------------------------------------------------------------- // Name: GetCalloutText // Desc: Returns the text associated with a specific callout // Args: dwObjId - Object ID of the requested callout. This corresponds to the // dwType value returned by EnumDeviceObjects. // pstrText - Pointer to a string buffer which will collect the current // callout string. // dwSize - Maximum number of characters to copy into the buffer. // Ret: DI_OK - Success. // DIERR_INVALIDPARAM - Fail; Null pointer passed. // DIERR_NOTINITIALIZED - Fail; Init() must be called first. // DIERR_OBJECTNOTFOUND - Fail; Given ID not found in list of objects //----------------------------------------------------------------------------- HRESULT CDIDevImage::GetCalloutText( DWORD dwObjId, LPTSTR pstrText, DWORD dwSize ) { // Verify initialization if( false == m_bInitialized ) return DIERR_NOTINITIALIZED; if( NULL == pstrText ) return DIERR_INVALIDPARAM; CDIDIObject* pObject = GetObject( dwObjId ); if( NULL == pObject ) return DIERR_OBJECTNOTFOUND; pObject->GetCalloutText( pstrText, dwSize ); return DI_OK; } //----------------------------------------------------------------------------- // Name: GetObjFromPoint // Desc: Returns the ID of the object on the device corresponding to the // callout which contains the given point. // Args: Pt - Point to check against callout rect coordinates // pdwObjId - Pointer to a variable which will contain the object ID upon // successful return. // Ret: DI_OK - Success. // DIERR_INVALIDPARAM - Fail; Null pointer passed. // DIERR_NOTINITIALIZED - Fail; Init() must be called first. // DIERR_OBJECTNOTFOUND - Fail; Given ID not found in list of objects //----------------------------------------------------------------------------- HRESULT CDIDevImage::GetObjFromPoint( POINT Pt, LPDWORD pdwObjId ) { // Verify initialization if( false == m_bInitialized ) return DIERR_NOTINITIALIZED; if( NULL == pdwObjId ) return DIERR_INVALIDPARAM; if( m_dwActiveView > m_dwNumViews ) return E_FAIL; // For a custom UI, this method depends on the UI being calculated if( m_bCustomUI && m_bInvalidUI ) BuildCustomUI(); DIDICallout *pCallout = NULL; // for each object for( UINT i=0; i < m_dwNumObjects; i++ ) { pCallout = m_apObject[i]->GetCallout( m_dwActiveView ); if( PtInRect( &(pCallout->rcScaled), Pt ) ) { // if the point is inside the scaled bounding rect, the // correct callout has been found. *pdwObjId = m_apObject[i]->GetID(); return DI_OK; } } return S_FALSE; } //----------------------------------------------------------------------------- // Name: SetActiveView // Desc: Activates the provided view. This view will be painted when the // device image is rendered. // Args: dwView - The index of the new view. // Ret: DI_OK - Success. // DIERR_INVALIDPARAM - Fail; View is out of range. // DIERR_NOTINITIALIZED - Fail; Init() must be called first. //----------------------------------------------------------------------------- HRESULT CDIDevImage::SetActiveView( DWORD dwView ) { // Verify initialization if( false == m_bInitialized ) return DIERR_NOTINITIALIZED; // For a custom UI, this method depends on the UI being calculated if( m_bCustomUI && m_bInvalidUI ) BuildCustomUI(); // Make sure view exists if( dwView >= m_dwNumViews ) return DIERR_INVALIDPARAM; m_dwActiveView = dwView; return DI_OK; } //----------------------------------------------------------------------------- // Name: GetActiveView // Desc: Returns the index of the currently active view. The active view is // what CDIDevImage paints when the device image is rendered. // Args: pdwView - Pointer to a variable which will contain the current view // index upon successful return. May be NULL. // pdwNumViews - Pointer to a variable which will contain the total // number of views upon successful return. May be NULL. // Ret: DI_OK - Success. // DIERR_NOTINITIALIZED - Fail; Init() must be called first. //----------------------------------------------------------------------------- HRESULT CDIDevImage::GetActiveView( LPDWORD pdwView, LPDWORD pdwNumViews ) { // Verify initialization if( false == m_bInitialized ) return DIERR_NOTINITIALIZED; // For a custom UI, this method depends on the UI being calculated if( m_bCustomUI && m_bInvalidUI ) BuildCustomUI(); if( pdwView ) *pdwView = m_dwActiveView; if( pdwNumViews ) *pdwNumViews = m_dwNumViews; return DI_OK; } //----------------------------------------------------------------------------- // Name: GetViewForObj // Desc: Returns the index of the most appropriate view for a specific object // Args: dwObjId - Object ID of the requested callout. This corresponds to the // dwType value returned by EnumDeviceObjects. // pdwView - Pointer to a variable which will contain an appropriate // view for the given object after return. May be NULL. // Ret: DI_OK - Success. // DIERR_NOTINITIALIZED - Fail; Init() must be called first. // DIERR_INVALIDPARAM - Fail; Null pointer. // DIERR_OBJECTNOTFOUND - Fail; Given ID not found in list of objects //----------------------------------------------------------------------------- HRESULT CDIDevImage::GetViewForObj( DWORD dwObjId, LPDWORD pdwView ) { UINT i=0; // Loop variable // Verify initialization if( false == m_bInitialized ) return DIERR_NOTINITIALIZED; if( NULL == pdwView ) return DIERR_INVALIDPARAM; CDIDIObject* pObject = GetObject( dwObjId ); if( NULL == pObject ) return DIERR_OBJECTNOTFOUND; // For a custom UI, this method depends on the UI being calculated if( m_bCustomUI && m_bInvalidUI ) BuildCustomUI(); // The method used to determine the best view for a particular object // is simple: The view which has the largest overlay rectangle probably // has the best view for an object. If there are no overlays for the // given object, use the view with the largest callout rectangle, or // the base view (0) if there are no callouts for the given object. DWORD dwCalloutMax = 0; DWORD dwCalloutIndex = 0; DWORD dwOverlayMax = 0; DWORD dwOverlayIndex = 0; for( i=0; i < m_dwNumViews; i++ ) { DWORD dwArea = 0; DIDIOverlay *pOverlay = pObject->GetOverlay( i ); DIDICallout *pCallout = pObject->GetCallout( i ); dwArea = ( pOverlay->rcScaled.right - pOverlay->rcScaled.left ) * ( pOverlay->rcScaled.bottom - pOverlay->rcScaled.top ); if( dwArea > dwOverlayMax ) { dwOverlayMax = dwArea; dwOverlayIndex = i; } dwArea = ( pCallout->rcScaled.right - pCallout->rcScaled.left ) * ( pCallout->rcScaled.bottom - pCallout->rcScaled.top ); if( dwArea > dwCalloutMax ) { dwCalloutMax = dwArea; dwCalloutIndex = i; } } // If an overlay rectangle was found, use the overlay index; otherwise, // use the callout index (this will be 0 if no callouts were found ). *pdwView = ( dwOverlayMax > 0 ) ? dwOverlayIndex : dwCalloutIndex; return DI_OK; } //----------------------------------------------------------------------------- // Name: SetOutputImageSize // Desc: Sets the size of the image that CDIDevImage will paint and output to // the application // Args: dwWidth - Preferred width. // dwHeight - Preferred height. // dwFlags - Scaling flag. Must be one of following: // DIDISOIS_DEFAULT - dwWidth and dwHeight values are ignored. The image // will not be scaled after loaded from disk; for created UIs, the // size is determined by the global constants. This is the default // value. // DIDISOIS_RESIZE - Scale the image and callouts to the given width // and height. // DIDISOIS_MAINTAINASPECTUSINGWIDTH - Scale the image and callouts to // the given width, but maintain the aspect ratio. The dwHeight // argument is ignored // DIDISOIS_MAINTAINASPECTUSINGHEIGHT - Scale the image and callouts to // the given height, but maintain the aspect ratio. The dwWidth // argument is ignored. // Ret: DI_OK - Success. // DIERR_NOTINITIALIZED - Fail; Init() must be called first. //----------------------------------------------------------------------------- HRESULT CDIDevImage::SetOutputImageSize( DWORD dwWidth, DWORD dwHeight, DWORD dwFlags ) { // Verify initialization if( false == m_bInitialized ) return DIERR_NOTINITIALIZED; // Store arguments m_dwWidthPref = dwWidth; m_dwHeightPref = dwHeight; m_dwScaleMethod = dwFlags; // If the image size has changed, all the stored images are no longer valid DestroyImages(); // This action might change the layout for the custom UI if( m_bCustomUI ) m_bInvalidUI = TRUE; return DI_OK; } //----------------------------------------------------------------------------- // Name: SetFont // Desc: Sets the font to be used for the callout text when rendering the image // Args: hFont - Handle to a GDI font object. The font's properties will be // copied and used for drawn text. // Ret: DI_OK - Success. // DIERR_INVALIDPARAM - Fail; Invalid handle. // DIERR_NOTINITIALIZED - Fail; Init() must be called first. //----------------------------------------------------------------------------- HRESULT CDIDevImage::SetFont( HFONT hFont ) { LOGFONT logfont = {0}; HFONT hNewFont = NULL; // Verify initialization if( false == m_bInitialized ) return DIERR_NOTINITIALIZED; // Retrieve the logfont and create a new member font if( 0 == ::GetObject( hFont, sizeof(LOGFONT), &logfont ) ) return DIERR_INVALIDPARAM; // Create a duplicate font hNewFont = CreateFontIndirect( &logfont ); if( NULL == hNewFont ) return E_FAIL; // Remove the current font if( m_hFont ) DeleteObject( m_hFont ); // Copy the font handle m_hFont = hNewFont; // This action might change the layout for the custom UI if( m_bCustomUI ) m_bInvalidUI = TRUE; return DI_OK; } //----------------------------------------------------------------------------- // Name: SetColors // Desc: Assigns a set of colors to use when painting the various items on the // image // Args: Background - Specifies the color and alpha component for the // image background. Device images are stored in a format which allows // the background to be replaced. D3D surfaces allow this background // to contain transparency information. Alpha values of 0 thru 254 allow // varying transparency effects on the output surfaces. A value // of 255 is treated specially during alpha blending, such that the // final output image will fully opaque. // crColorNormal - Foreground text color for callout strings in a normal // state. // crColorHigh - Foreground text color for callout strings in a // highlighted state. // Ret: DI_OK - Success. // DIERR_NOTINITIALIZED - Fail; Init() must be called first. //----------------------------------------------------------------------------- HRESULT CDIDevImage::SetColors( D3DCOLOR Background, COLORREF crCalloutNormal, COLORREF crCalloutHigh ) { // Verify initialization if( false == m_bInitialized ) return DIERR_NOTINITIALIZED; // If the background is changing colors, the images will have to be // reloaded. As an optimization, the background color is only applied // when the image is loaded since the background won't change as often as // the image is rendered. if( m_BkColor != Background ) DestroyImages(); m_BkColor = Background; for( UINT i=0; i < m_dwNumObjects; i++ ) { m_apObject[i]->SetCalloutColors( crCalloutNormal, crCalloutHigh ); } return DI_OK; } //----------------------------------------------------------------------------- // Name: Render // Desc: Renders an image of the device and its callouts onto a Direct3DTexture // Args: pTexture - Pointer to a D3D Texture Object on which to paint the UI. // Ret: DI_OK - Success. // DIERR_INVALIDPARAM - Fail; Null pointer. // DIERR_NOTINITIALIZED - Fail; Init() must be called first. //----------------------------------------------------------------------------- HRESULT CDIDevImage::Render( LPDIRECT3DTEXTURE9 pTexture ) { HRESULT hr = DI_OK; LPDIRECT3DSURFACE9 pSurface = NULL; // Verify initialization if( false == m_bInitialized ) return DIERR_NOTINITIALIZED; // Check parameters if( NULL == pTexture ) return DIERR_INVALIDPARAM; // Add a reference to the passed texture pTexture->AddRef(); // Extract the surface hr = pTexture->GetSurfaceLevel( 0, &pSurface ); if( FAILED(hr) ) goto LCleanReturn; // Perform the render if( m_bCustomUI ) hr = RenderCustomToTarget( (LPVOID) pSurface, DIDIRT_SURFACE ); else hr = RenderToTarget( (LPVOID) pSurface, DIDIRT_SURFACE ); LCleanReturn: SAFE_RELEASE( pSurface ); SAFE_RELEASE( pTexture ); return hr; } //----------------------------------------------------------------------------- // Name: RenderToDC // Desc: Renders an image of the device and its callouts onto a GDI device // object // Args: hDC - Handle to a device context on which to paint the UI. // Ret: DI_OK - Success. // DIERR_NOTINITIALIZED - Fail; Init() must be called first. //----------------------------------------------------------------------------- HRESULT CDIDevImage::RenderToDC( HDC hDC ) { HRESULT hr = S_OK; // Verify initialization if( false == m_bInitialized ) return DIERR_NOTINITIALIZED; if( m_bCustomUI ) hr = RenderCustomToTarget( (LPVOID) hDC, DIDIRT_DC ); else hr = RenderToTarget( (LPVOID) hDC, DIDIRT_DC ); return hr; } //----------------------------------------------------------------------------- // Name: RenderCustomToTarget // Desc: Renders a custom UI //----------------------------------------------------------------------------- HRESULT CDIDevImage::RenderCustomToTarget( LPVOID pvTarget, DIDIRENDERTARGET eTarget ) { HRESULT hr = DI_OK; UINT i = 0; HDC hdcRender = NULL; HDC hdcAlpha = NULL; RECT rcBitmap = {0}; DIBSECTION info = {0}; HBITMAP hbmpRender = NULL; HBITMAP hbmpAlpha = NULL; SIZE size = {0}; // Get the UI dimensions GetCustomUISize( &size ); if( m_bInvalidUI ) BuildCustomUI(); // Create a background image BITMAPINFO bmi = {0}; bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = size.cx; bmi.bmiHeader.biHeight = - (int) size.cy; // top-down bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; hdcRender = CreateCompatibleDC( NULL ); if( NULL == hdcRender ) { hr = DIERR_OUTOFMEMORY; goto LCleanReturn; } hdcAlpha = CreateCompatibleDC( NULL ); if( NULL == hdcAlpha ) { hr = DIERR_OUTOFMEMORY; goto LCleanReturn; } hbmpRender = CreateDIBSection( hdcRender, &bmi, DIB_RGB_COLORS, NULL, NULL, NULL ); if( hbmpRender == NULL ) { hr = DIERR_OUTOFMEMORY; goto LCleanReturn; } if( 0 == ::GetObject( hbmpRender, sizeof( DIBSECTION ), &info ) ) { hr = E_FAIL; goto LCleanReturn; } SelectObject( hdcRender, hbmpRender ); SelectObject( hdcRender, m_hFont ); SetBkMode( hdcRender, TRANSPARENT ); FillBackground( hbmpRender, m_BkColor ); // Create a bitmap to store the alpha channel for the bitmap. Since GDI // doesn't support alpha information, everything is drawn fully transparent. // As a workaround, whenever a 2D method is called on the render dc, // the same method will be called on the alpha dc. Before transfering the // image to the provided surface, the transparency information will be // restored from the alpha bitmap. hbmpAlpha = CreateDIBSection( hdcAlpha, &bmi, DIB_RGB_COLORS, NULL, NULL, NULL ); if( NULL == hbmpAlpha ) { hr = DIERR_OUTOFMEMORY; goto LCleanReturn; } // Clear the alpha channel DIBSECTION infoAlpha; if( 0 == ::GetObject( hbmpAlpha, sizeof(DIBSECTION), &infoAlpha ) ) { hr = E_FAIL; goto LCleanReturn; } ZeroMemory( infoAlpha.dsBm.bmBits, infoAlpha.dsBm.bmWidthBytes * infoAlpha.dsBm.bmHeight ); SelectObject( hdcAlpha, hbmpAlpha ); SetBkMode( hdcAlpha, TRANSPARENT ); SetTextColor( hdcAlpha, RGB(255, 255, 255) ); SelectObject( hdcAlpha, GetStockObject( WHITE_PEN ) ); SelectObject( hdcAlpha, m_hFont ); // Draw callout and object text for( i=0; i < m_dwNumObjects; i++ ) { COLORREF crNorm, crHigh, crCur; DIDICallout *pCallout = m_apObject[i]->GetCallout( m_dwActiveView ); DIDIOverlay *pOverlay = m_apObject[i]->GetOverlay( m_dwActiveView ); MAXSTRING strCallout = {0}; MAXSTRING strObject = {0}; RECT rcFill = {0}; // Callout may be invisible if( DIDICOS_INVISIBLE & m_apObject[i]->GetCalloutState() ) continue; if( IsRectEmpty( &pOverlay->rcScaled ) || IsRectEmpty( &pCallout->rcScaled ) ) continue; m_apObject[i]->GetCalloutText( strCallout, MAX_PATH ); m_apObject[i]->GetName( strObject, MAX_PATH ); m_apObject[i]->GetCalloutColors( &crNorm, &crHigh ); crCur = ( DIDICOS_HIGHLIGHTED & m_apObject[i]->GetCalloutState() ) ? crHigh : crNorm; SetTextColor( hdcRender, crNorm ); DWORD dwFlags = DT_TOP | DT_END_ELLIPSIS | DT_NOCLIP; // Get the fill rect rcFill = pOverlay->rcScaled; DrawText( hdcRender, strObject, lstrlen( strObject ), &rcFill, dwFlags | DT_CALCRECT ); // Position the fill rect rcFill.left += pOverlay->rcScaled.right - rcFill.right; rcFill.right += pOverlay->rcScaled.right - rcFill.right; // Inflate the fill rect InflateRect( &rcFill, 5, 5 ); // But make sure the rect still fits on the screen rcFill.left = max( rcFill.left, 0 ); rcFill.top = max( rcFill.top, 0 ); rcFill.right = min( rcFill.right, info.dsBm.bmWidth ); rcFill.bottom = min( rcFill.bottom, info.dsBm.bmHeight ); // Draw the object text DrawText( hdcRender, strObject, lstrlen( strObject ), &(pOverlay->rcScaled), dwFlags | DT_RIGHT ); if( hdcAlpha ) DrawText( hdcAlpha, strObject, lstrlen( strObject ), &(pOverlay->rcScaled), dwFlags | DT_RIGHT ); SetTextColor( hdcRender, crCur ); // Get the fill rect rcFill = pCallout->rcScaled; DrawText( hdcRender, strCallout, lstrlen( strCallout ), &rcFill, dwFlags | DT_CALCRECT ); // Inflate the fill rect InflateRect( &rcFill, 5, 5 ); // But make sure the rect still fits on the screen rcFill.left = max( rcFill.left, 0 ); rcFill.top = max( rcFill.top, 0 ); rcFill.right = min( rcFill.right, info.dsBm.bmWidth ); rcFill.bottom = min( rcFill.bottom, info.dsBm.bmHeight ); // Draw the callout text DrawText( hdcRender, strCallout, lstrlen( strCallout ), &(pCallout->rcScaled), dwFlags | DT_LEFT ); if( hdcAlpha ) DrawText( hdcAlpha, strCallout, lstrlen( strCallout ), &(pCallout->rcScaled), dwFlags | DT_LEFT ); // If the TOOLTIP flag is set and the callout text doesn't fit within // the scaled rect, we need to draw the full text if( DIDICOS_TOOLTIP & m_apObject[i]->GetCalloutState() ) { SIZE TextSize = {0}; // This string was modified by the first call to draw text, so we // need to get a fresh copy m_apObject[i]->GetCalloutText( strCallout, MAX_PATH-4 ); GetTextExtentPoint32( hdcRender, strCallout, lstrlen( strCallout ), &TextSize ); if( TextSize.cx > ( pCallout->rcScaled.right - pCallout->rcScaled.left ) ) { // Yep, the text is too big and is marked as a tooltip candidate. RECT rcBitmap = { 0, 0, info.dsBm.bmWidth, info.dsBm.bmHeight }; DrawTooltip( hdcRender, hdcAlpha, strCallout, &rcBitmap, &(pCallout->rcScaled), CRFromColor( m_BkColor ), crNorm, crHigh ); } } } // Finalize rendering GdiFlush(); // Copy the freshly rendered image to the render target switch( eTarget ) { case DIDIRT_SURFACE: // Since the image is being transfered to a Direct3D surface, the stored // alpha information could be used. ApplyAlphaChannel( hbmpRender, hbmpAlpha, ( (m_BkColor & ALPHA_MASK) == ALPHA_MASK ) ); rcBitmap.right = info.dsBm.bmWidth; rcBitmap.bottom = info.dsBm.bmHeight; hr = D3DXLoadSurfaceFromMemory( (LPDIRECT3DSURFACE9) pvTarget, NULL, NULL, info.dsBm.bmBits, D3DFMT_A8R8G8B8, info.dsBm.bmWidthBytes, NULL, &rcBitmap, D3DX_FILTER_NONE, 0 ); break; case DIDIRT_DC: BitBlt( (HDC) pvTarget, 0, 0, info.dsBm.bmWidth, info.dsBm.bmHeight, hdcRender, 0, 0, SRCCOPY ); break; default: // Invalid render target hr = DIERR_INVALIDPARAM; goto LCleanReturn; } LCleanReturn: DeleteDC( hdcRender ); DeleteDC( hdcAlpha ); if( hbmpAlpha ) DeleteObject( hbmpAlpha ); if( hbmpRender ) DeleteObject( hbmpRender ); return hr; } //----------------------------------------------------------------------------- // Name: RenderToTarget // Desc: Renders an image of the device and its callouts //----------------------------------------------------------------------------- HRESULT CDIDevImage::RenderToTarget( LPVOID pvTarget, DIDIRENDERTARGET eTarget ) { HRESULT hr = DI_OK; UINT i = 0; // Loop variable RECT rcBitmap = {0}; HDC hdcRender = NULL; HDC hdcAlpha = NULL; DIBSECTION info = {0}; LPBYTE pSavedPixels = NULL; LPBYTE pCleanPixels = NULL; HBITMAP hbmpAlpha = NULL; BITMAPINFO bmi = {0}; // Verify initialization if( false == m_bInitialized ) return DIERR_NOTINITIALIZED; // Verify parameters if( NULL == pvTarget ) return DIERR_INVALIDPARAM; // Sanity check if( m_dwActiveView >= m_dwNumViews ) return E_FAIL; // Load images if not loaded already if( NULL == m_ahImages[ m_dwActiveView ] ) { // File UI not yet loaded if( FAILED( hr = LoadImages() ) ) return hr; } // Get information about the background image. if( 0 == ::GetObject( m_ahImages[ m_dwActiveView ], sizeof(DIBSECTION), &info ) ) return E_FAIL; // Allocate space for the saved background images pSavedPixels = new BYTE[ info.dsBm.bmWidthBytes * info.dsBm.bmHeight ]; if( NULL == pSavedPixels ) { hr = DIERR_OUTOFMEMORY; goto LCleanReturn; } pCleanPixels = new BYTE[ info.dsBm.bmWidthBytes * info.dsBm.bmHeight ]; if( NULL == pCleanPixels ) { // Could not create a copy of the background image; release memory // here to avoid using unitialized pixels during cleanup. SAFE_DELETE_ARRAY( pSavedPixels ); hr = DIERR_OUTOFMEMORY; goto LCleanReturn; } // Save the background CopyMemory( pSavedPixels, info.dsBm.bmBits, info.dsBm.bmWidthBytes * info.dsBm.bmHeight ); // Draw overlays for( i=0; i < m_dwNumObjects; i++ ) { DIDIOverlay *pOverlay = m_apObject[i]->GetOverlay( m_dwActiveView ); if( DIDICOS_HIGHLIGHTED & m_apObject[i]->GetCalloutState() ) { // Draw overlay if( pOverlay->hImage ) ApplyOverlay( m_ahImages[ m_dwActiveView ], &pOverlay->rcScaled, pOverlay->hImage ); } } // Before drawing callouts and lines on top of the composed image, save // a copy of the image bits. This will allow us to erase portions of lines // which intersect with the callout text. CopyMemory( pCleanPixels, info.dsBm.bmBits, info.dsBm.bmWidthBytes * info.dsBm.bmHeight ); // Load the background image into a device context for rendering hdcRender = CreateCompatibleDC( NULL ); SelectObject( hdcRender, m_ahImages[ m_dwActiveView ] ); SelectObject( hdcRender, m_hFont ); SetBkMode( hdcRender, TRANSPARENT ); SetBkColor( hdcRender, CRFromColor(m_BkColor) ); hdcAlpha = CreateCompatibleDC( NULL ); // Create a bitmap to store the alpha channel for the bitmap. Since GDI // doesn't support alpha information, everything is drawn fully transparent. // As a workaround, whenever a 2D method is called on the render dc, // the same method will be called on the alpha dc. Before transfering the // image to the provided surface, the transparency information will be // restored from the alpha bitmap. bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = info.dsBm.bmWidth; bmi.bmiHeader.biHeight = - (int) info.dsBm.bmHeight; // top-down bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; hbmpAlpha = CreateDIBSection( hdcAlpha, &bmi, DIB_RGB_COLORS, NULL, NULL, NULL ); if( NULL == hbmpAlpha ) { hr = DIERR_OUTOFMEMORY; goto LCleanReturn; } // Clear the alpha channel DIBSECTION infoAlpha; if( 0 == ::GetObject( hbmpAlpha, sizeof(DIBSECTION), &infoAlpha ) ) { hr = E_FAIL; goto LCleanReturn; } ZeroMemory( infoAlpha.dsBm.bmBits, infoAlpha.dsBm.bmWidthBytes * infoAlpha.dsBm.bmHeight ); SelectObject( hdcAlpha, hbmpAlpha ); SetBkMode( hdcAlpha, TRANSPARENT ); SetTextColor( hdcAlpha, RGB(255, 255, 255) ); SelectObject( hdcAlpha, GetStockObject( WHITE_PEN ) ); SelectObject( hdcAlpha, m_hFont ); // Draw callout lines for( i=0; i < m_dwNumObjects; i++ ) { COLORREF crNorm, crHigh, crCur; POINT aptArrow[2] = {0}; BOOL bDrawArrow = FALSE; // Get the current callout DIDICallout *pCallout = m_apObject[i]->GetCallout( m_dwActiveView ); // Callout may be invisible if( DIDICOS_INVISIBLE & m_apObject[i]->GetCalloutState() ) continue; // Retrieve normal/highlighted colors m_apObject[i]->GetCalloutColors( &crNorm, &crHigh ); // Set the current color based on callout state crCur = ( DIDICOS_HIGHLIGHTED & m_apObject[i]->GetCalloutState() ) ? crHigh : crNorm; DeleteObject( SelectObject( hdcRender, CreatePen( PS_SOLID, 3, CRFromColor(m_BkColor) ) ) ); DeleteObject( SelectObject( hdcAlpha, CreatePen( PS_SOLID, 3, RGB(255, 255, 255) ) ) ); // Draw callout lines MoveToEx( hdcRender, pCallout->aptLineScaled[0].x, pCallout->aptLineScaled[0].y, NULL ); MoveToEx( hdcAlpha, pCallout->aptLineScaled[0].x, pCallout->aptLineScaled[0].y, NULL ); for( UINT j=1; j < pCallout->dwNumPoints; j++ ) { LineTo( hdcRender, pCallout->aptLineScaled[j].x, pCallout->aptLineScaled[j].y ); LineTo( hdcAlpha, pCallout->aptLineScaled[j].x, pCallout->aptLineScaled[j].y ); } // Draw arrow ends if( pCallout->dwNumPoints >= 2 ) { DWORD dwEnd = pCallout->dwNumPoints-1; POINT p1 = pCallout->aptLineScaled[ dwEnd ]; POINT p2 = pCallout->aptLineScaled[ dwEnd-1 ]; aptArrow[0] = aptArrow[1] = p1; bDrawArrow = TRUE; aptArrow[0].x -= 1; aptArrow[0].y -= 1; aptArrow[1].x += 1; aptArrow[1].y += 1; // Adjust arrow points based on line orientation if( p1.y == p2.y ) { if( p2.x < p1.x ) aptArrow[1].x -= 2; else aptArrow[0].x += 2; } else if( p1.x == p2.x ) { if( p2.y < p1.y ) aptArrow[1].y -= 2; else aptArrow[0].y += 2; } else { // This is a diagonal line. Skip the arrow endpoint. bDrawArrow = FALSE; } if( bDrawArrow ) { MoveToEx( hdcRender, aptArrow[0].x, aptArrow[0].y, NULL ); LineTo( hdcRender, aptArrow[1].x, aptArrow[1].y ); MoveToEx( hdcAlpha, aptArrow[0].x, aptArrow[0].y, NULL ); LineTo( hdcAlpha, aptArrow[1].x, aptArrow[1].y ); } } // Select a new pen into the DC based on current color DeleteObject( SelectObject( hdcRender, CreatePen( PS_SOLID, 1, crCur ) ) ); DeleteObject( SelectObject( hdcAlpha, GetStockObject( WHITE_PEN ) ) ); // Draw callout lines MoveToEx( hdcRender, pCallout->aptLineScaled[0].x, pCallout->aptLineScaled[0].y, NULL ); MoveToEx( hdcAlpha, pCallout->aptLineScaled[0].x, pCallout->aptLineScaled[0].y, NULL ); for( j=1; j < pCallout->dwNumPoints; j++ ) { LineTo( hdcRender, pCallout->aptLineScaled[j].x, pCallout->aptLineScaled[j].y ); LineTo( hdcAlpha, pCallout->aptLineScaled[j].x, pCallout->aptLineScaled[j].y ); } // Draw arrow ends if( bDrawArrow ) { DWORD dwEnd = pCallout->dwNumPoints-1; SetPixel( hdcRender, aptArrow[0].x, aptArrow[0].y, crCur ); SetPixel( hdcRender, aptArrow[1].x, aptArrow[1].y, crCur ); SetPixel( hdcRender, pCallout->aptLineScaled[ dwEnd ].x, pCallout->aptLineScaled[ dwEnd ].y, crCur ); SetPixel( hdcAlpha, aptArrow[0].x, aptArrow[0].y, RGB(255, 255, 255) ); SetPixel( hdcAlpha, aptArrow[1].x, aptArrow[1].y, RGB(255, 255, 255) ); SetPixel( hdcAlpha, pCallout->aptLineScaled[ dwEnd ].x, pCallout->aptLineScaled[ dwEnd ].y, RGB(255, 255, 255) ); } } // Free the pen resource DeleteObject( SelectObject( hdcRender, GetStockObject( WHITE_PEN ) ) ); DeleteObject( SelectObject( hdcAlpha, GetStockObject( WHITE_PEN ) ) ); // Draw callout text for( i=0; i < m_dwNumObjects; i++ ) { COLORREF crNorm, crHigh, crCur; RECT rcFill; DIDICallout *pCallout = m_apObject[i]->GetCallout( m_dwActiveView ); MAXSTRING strCallout = {0}; // Callout may be invisible if( DIDICOS_INVISIBLE & m_apObject[i]->GetCalloutState() ) continue; m_apObject[i]->GetCalloutText( strCallout, MAX_PATH-4 ); m_apObject[i]->GetCalloutColors( &crNorm, &crHigh ); if( IsRectEmpty( &pCallout->rcScaled ) ) continue; // Draw callouts DWORD dwFormat = DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOCLIP; // Get text dimensions rcFill = pCallout->rcScaled; DrawText( hdcRender, strCallout, lstrlen( strCallout ), &rcFill, dwFormat | DT_CALCRECT | DT_MODIFYSTRING ); // Horizontal alignment if( pCallout->dwTextAlign & DIDAL_CENTERED ) { dwFormat |= DT_CENTER; OffsetRect( &rcFill, (pCallout->rcScaled.right - rcFill.right) / 2, 0 ); } else if( pCallout->dwTextAlign & DIDAL_RIGHTALIGNED ) { dwFormat |= DT_RIGHT; OffsetRect( &rcFill, (pCallout->rcScaled.right - rcFill.right ), 0 ); } else { dwFormat |= DT_LEFT; } // Vertical alignment if( pCallout->dwTextAlign & DIDAL_MIDDLE ) { dwFormat |= DT_VCENTER; OffsetRect( &rcFill, 0, (pCallout->rcScaled.bottom - rcFill.bottom) / 2 ); } else if( pCallout->dwTextAlign & DIDAL_BOTTOMALIGNED ) { dwFormat |= DT_BOTTOM; OffsetRect( &rcFill, 0, (pCallout->rcScaled.bottom - rcFill.bottom) ); } else { dwFormat |= DT_TOP; } // First replace the background area behind the text to cover up // intersecting lines // Pad the returned rect InflateRect( &rcFill, 5, 5 ); // But make sure the rect still fits on the screen rcFill.left = max( rcFill.left, 0 ); rcFill.top = max( rcFill.top, 0 ); rcFill.right = min( rcFill.right, info.dsBm.bmWidth ); rcFill.bottom = min( rcFill.bottom, info.dsBm.bmHeight ); RestoreRect( m_ahImages[ m_dwActiveView ], &rcFill, pCleanPixels ); if( hdcAlpha ) FillRect( hdcAlpha, &rcFill, (HBRUSH) GetStockObject( BLACK_BRUSH ) ); // Fill behind the text SetTextColor( hdcRender, CRFromColor(m_BkColor) ); for( int x = -1; x <= 1; x++ ) { for( int y = -1; y <= 1; y++ ) { RECT rcText = pCallout->rcScaled; OffsetRect( &rcText, x, y ); DrawText( hdcRender, strCallout, lstrlen( strCallout ), &rcText, dwFormat ); DrawText( hdcAlpha, strCallout, lstrlen( strCallout ), &rcText, dwFormat ); } } // Now draw the actual text crCur = ( DIDICOS_HIGHLIGHTED & m_apObject[i]->GetCalloutState() ) ? crHigh : crNorm; SetTextColor( hdcRender, crCur ); DrawText( hdcRender, strCallout, lstrlen( strCallout ), &(pCallout->rcScaled), dwFormat ); DrawText( hdcAlpha, strCallout, lstrlen( strCallout ), &(pCallout->rcScaled), dwFormat ); // If the TOOLTIP flag is set and the callout text doesn't fit within // the scaled rect, we need to draw the full text if( DIDICOS_TOOLTIP & m_apObject[i]->GetCalloutState() ) { SIZE TextSize = {0}; // This string was modified by the first call to draw text, so we // need to get a fresh copy m_apObject[i]->GetCalloutText( strCallout, MAX_PATH-4 ); GetTextExtentPoint32( hdcRender, strCallout, lstrlen( strCallout ), &TextSize ); if( TextSize.cx > ( pCallout->rcScaled.right - pCallout->rcScaled.left ) ) { // Yep, the text is too big and is marked as a tooltip candidate. RECT rcBitmap = { 0, 0, info.dsBm.bmWidth, info.dsBm.bmHeight }; DrawTooltip( hdcRender, hdcAlpha, strCallout, &rcBitmap, &(pCallout->rcScaled), CRFromColor( m_BkColor ), crNorm, crHigh ); } } } // Finalize all rendering GdiFlush(); // Copy the freshly rendered image to the render target switch( eTarget ) { case DIDIRT_SURFACE: // Since the image is being transfered to a Direct3D surface, the stored // alpha information could be used. ApplyAlphaChannel( m_ahImages[ m_dwActiveView ], hbmpAlpha, ( (m_BkColor & ALPHA_MASK) == ALPHA_MASK ) ); rcBitmap.right = info.dsBm.bmWidth; rcBitmap.bottom = info.dsBm.bmHeight; hr = D3DXLoadSurfaceFromMemory( (LPDIRECT3DSURFACE9) pvTarget, NULL, NULL, info.dsBm.bmBits, D3DFMT_A8R8G8B8, info.dsBm.bmWidthBytes, NULL, &rcBitmap, D3DX_FILTER_NONE, 0 ); break; case DIDIRT_DC: BitBlt( (HDC) pvTarget, 0, 0, info.dsBm.bmWidth, info.dsBm.bmHeight, hdcRender, 0, 0, SRCCOPY ); break; default: // Invalid render target hr = DIERR_INVALIDPARAM; goto LCleanReturn; } LCleanReturn: // Restore the background if( pSavedPixels ) CopyMemory( info.dsBm.bmBits, pSavedPixels, info.dsBm.bmWidthBytes * info.dsBm.bmHeight ); DeleteDC( hdcRender ); DeleteDC( hdcAlpha ); DeleteObject( hbmpAlpha ); delete [] pSavedPixels; delete [] pCleanPixels; return hr; } //----------------------------------------------------------------------------- // Name: AddObject // Desc: Adds an object to the current list according to object ID. If an // object with the given ID already exists, a pointer to it returned. // Otherwise, a new object is created and a pointer to the new object // is returned. Returns NULL if memory couldn't be allocated. //----------------------------------------------------------------------------- HRESULT CDIDevImage::AddObject( DWORD dwID ) { CDIDIObject* pObject = NULL; // Search through current objects if( GetObject( dwID ) ) return DI_OK; // Did not find object. Create a new object, and store pointer pObject = new CDIDIObject( dwID, m_dwNumViews ); if( NULL == pObject ) return DIERR_OUTOFMEMORY; m_apObject[m_dwNumObjects++] = pObject; return DI_OK; } //----------------------------------------------------------------------------- // Name: GetObject // Desc: If an object with given ID exist, a pointer to it is returned //----------------------------------------------------------------------------- CDIDIObject* CDIDevImage::GetObject( DWORD dwID ) { for( UINT i=0; i < m_dwNumObjects; i++ ) { if( m_apObject[i]->GetID() == dwID ) return m_apObject[i]; } return NULL; } //----------------------------------------------------------------------------- // Name: LoadImageInfo // Desc: helper function to retrieve callout / image data from // DirectInput //----------------------------------------------------------------------------- HRESULT CDIDevImage::LoadImageInfo( LPDIRECTINPUTDEVICE8 pDIDevice ) { HRESULT hr; DWORD dwBufferCount = 0; DIDEVICEIMAGEINFOHEADER dihImageHeader = {0}; DIDEVICEIMAGEINFO *pInfo = NULL; // properly initialize the structure before it can be used dihImageHeader.dwSize = sizeof( DIDEVICEIMAGEINFOHEADER ); dihImageHeader.dwSizeImageInfo = sizeof( DIDEVICEIMAGEINFO ); // since m_dihImageHeader.dwBufferSize is 0, this call serves to determine // the minimum buffer size required to hold information for all the images hr = pDIDevice->GetImageInfo( &dihImageHeader ); if( FAILED(hr) ) return hr; // at this point, m_lpDidImgHeader->dwBufferUsed has been set by // the GetImageInfo method to minimum buffer size needed, so allocate. dihImageHeader.dwBufferSize = dihImageHeader.dwBufferUsed; dihImageHeader.lprgImageInfoArray = (DIDEVICEIMAGEINFO*) new BYTE[dihImageHeader.dwBufferSize]; // make sure memory has been allocated if( NULL == dihImageHeader.lprgImageInfoArray ) return DIERR_OUTOFMEMORY; // now that the dwBufferSize has been filled, and lprgImageArray allocated, // we call GetImageInfo again to get the image data hr = pDIDevice->GetImageInfo( &dihImageHeader ); if( FAILED(hr) ) goto LCleanReturn; // Allocate space for all the object/callouts/overlays m_apObject = new CDIDIObject* [dihImageHeader.dwcButtons + dihImageHeader.dwcAxes + dihImageHeader.dwcPOVs]; if( NULL == m_apObject ) { hr = DIERR_OUTOFMEMORY; goto LCleanReturn; } m_dwNumViews = dihImageHeader.dwcViews; // Allocate space for background images m_atszImagePath = new TCHAR[m_dwNumViews][MAX_PATH]; if( NULL == m_atszImagePath ) { hr = DIERR_OUTOFMEMORY; goto LCleanReturn; } ZeroMemory( m_atszImagePath, sizeof(m_atszImagePath) ); m_ahImages = new HBITMAP[m_dwNumViews]; if( NULL == m_ahImages ) { hr = DIERR_OUTOFMEMORY; goto LCleanReturn; } ZeroMemory( m_ahImages, sizeof(m_ahImages) ); // Fill the data from the ImageHeader pInfo = dihImageHeader.lprgImageInfoArray; dwBufferCount = dihImageHeader.dwBufferUsed; while( dwBufferCount > 0) { if( pInfo->dwViewID > m_dwNumViews ) { // Error in the input format, this is out of bounds for our array hr = E_FAIL; goto LCleanReturn; } if( pInfo->dwFlags & DIDIFT_CONFIGURATION ) { lstrcpy( m_atszImagePath[pInfo->dwViewID], pInfo->tszImagePath ); } else if( pInfo->dwFlags & DIDIFT_OVERLAY ) { hr = AddObject( pInfo->dwObjID ); if( FAILED(hr) ) goto LCleanReturn; CDIDIObject *pDIObj = GetObject( pInfo->dwObjID ); if( NULL == pDIObj ) { hr = DIERR_OUTOFMEMORY; goto LCleanReturn; } // Overlay if( pInfo->tszImagePath[0] ) pDIObj->SetOverlay( pInfo->dwViewID, pInfo->tszImagePath, pInfo->rcOverlay ); // Callout pDIObj->SetCallout( pInfo->dwViewID, pInfo->dwcValidPts, pInfo->rgptCalloutLine, pInfo->rcCalloutRect, pInfo->dwTextAlign ); } pInfo++; dwBufferCount -= dihImageHeader.dwSizeImageInfo; } // We made it this far, set the return value as success. hr = DI_OK; LCleanReturn: // Release the resources used for the image info structure delete [] dihImageHeader.lprgImageInfoArray; return hr; } //----------------------------------------------------------------------------- // Name: CreateCustomImageInfo // Desc: Create a default UI for the given device, and fill in all neccesary // structures to support rendering //----------------------------------------------------------------------------- HRESULT CDIDevImage::CreateCustomImageInfo( LPDIRECTINPUTDEVICE8 pDIDevice ) { HRESULT hr; DIDEVCAPS didc; // Allocate space for all the device's objects (axes, buttons, POVS) ZeroMemory( &didc, sizeof(DIDEVCAPS) ); didc.dwSize = sizeof(DIDEVCAPS); hr = pDIDevice->GetCapabilities( &didc ); if( FAILED(hr) ) return hr; m_apObject = new CDIDIObject* [didc.dwAxes + didc.dwButtons + didc.dwPOVs]; if( NULL == m_apObject ) return DIERR_OUTOFMEMORY; hr = pDIDevice->EnumObjects( EnumDeviceObjectsCB, this, DIDFT_AXIS ); if( FAILED(hr) ) return hr; hr = pDIDevice->EnumObjects( EnumDeviceObjectsCB, this, DIDFT_BUTTON ); if( FAILED(hr)) return hr; hr = pDIDevice->EnumObjects( EnumDeviceObjectsCB, this, DIDFT_POV ); if( FAILED(hr)) return hr; return DI_OK; } //----------------------------------------------------------------------------- // Name: LoadImages // Desc: Load all images associated with the active view //----------------------------------------------------------------------------- HRESULT CDIDevImage::LoadImages() { UINT i; HRESULT hr; SIZE sizeInit = {0}; SIZE sizeScaled = {0}; FLOAT fxScale, fyScale; D3DXIMAGE_INFO d3dxImageInfo; LPDIRECT3DSURFACE9 pLoadSurface = NULL; LPDIRECT3DSURFACE9 pScaleSurface = NULL; // Create a temporary surface pLoadSurface = GetCloneSurface( DIDICONST_MAX_IMAGE_WIDTH, DIDICONST_MAX_IMAGE_HEIGHT ); // Load the background image onto the temporary loading surface hr = D3DXLoadSurfaceFromFile( pLoadSurface, NULL, NULL, m_atszImagePath[m_dwActiveView], NULL, D3DX_FILTER_NONE, NULL, &d3dxImageInfo ); if( FAILED(hr) ) goto LCleanReturn; // The actual dimensions of the render surface are determined // by the background image, the overlay images, and the // callout rects. sizeInit.cx = d3dxImageInfo.Width; sizeInit.cy = d3dxImageInfo.Height; for( i=0; i < m_dwNumObjects; i++ ) { DIDICallout* pCallout = m_apObject[i]->GetCallout( m_dwActiveView ); sizeInit.cx = max( sizeInit.cx, pCallout->rcInit.right ); sizeInit.cy = max( sizeInit.cy, pCallout->rcInit.bottom ); } // Determine the scaling parameters switch( m_dwScaleMethod ) { case( DIDISOIS_RESIZE ) : sizeScaled.cx = m_dwWidthPref; sizeScaled.cy = m_dwHeightPref; break; case( DIDISOIS_MAINTAINASPECTUSINGWIDTH ) : sizeScaled.cx = m_dwWidthPref; sizeScaled.cy = (LONG) ( 0.5 + sizeInit.cy * ( (FLOAT)m_dwWidthPref / sizeInit.cx ) ); break; case( DIDISOIS_MAINTAINASPECTUSINGHEIGHT ) : sizeScaled.cx = (LONG) ( 0.5 + sizeInit.cx * ( (FLOAT)m_dwHeightPref / sizeInit.cy ) ); sizeScaled.cy = m_dwHeightPref; break; case( DIDISOIS_DEFAULT ) : default : sizeScaled.cx = sizeInit.cx; sizeScaled.cy = sizeInit.cy; break; } // Calculate scaling multipliers fxScale = (FLOAT)sizeScaled.cx / sizeInit.cx; fyScale = (FLOAT)sizeScaled.cy / sizeInit.cy; // Scale all object display parameters for( i=0; i < m_dwNumObjects; i++ ) { m_apObject[i]->ScaleView( m_dwActiveView, fxScale, fyScale ); } // Load the background image hr = CreateScaledSurfaceCopy( pLoadSurface, d3dxImageInfo.Width, d3dxImageInfo.Height, fxScale, fyScale, &pScaleSurface ); if( FAILED(hr) ) goto LCleanReturn; // Create a DIB section for the loaded image hr = CreateDIBSectionFromSurface( pScaleSurface, &(m_ahImages[ m_dwActiveView ]), &sizeScaled ); if( FAILED(hr) ) goto LCleanReturn; SAFE_RELEASE( pScaleSurface ); // Apply the background color FillBackground( m_ahImages[ m_dwActiveView ], m_BkColor ); // Load all object images for( i=0; i < m_dwNumObjects; i++ ) { DIDIOverlay *pOverlay = m_apObject[i]->GetOverlay( m_dwActiveView ); // Load the file onto the temporary surface if( !pOverlay->strImagePath[0] ) continue; hr = D3DXLoadSurfaceFromFile( pLoadSurface, NULL, NULL, pOverlay->strImagePath, NULL, D3DX_FILTER_NONE, NULL, &d3dxImageInfo ); if( FAILED(hr) ) continue; // Since overlay rectanges are actually defined by the image size, some // of the image info files simply define the top-left coordinate of the // rectangle. We may want good data for the overlay rectangle, so set // the rect based on image size pOverlay->rcInit.bottom = pOverlay->rcInit.top + d3dxImageInfo.Height; pOverlay->rcInit.right = pOverlay->rcInit.left + d3dxImageInfo.Width; ScaleRect( &( pOverlay->rcInit), &( pOverlay->rcScaled ), fxScale, fyScale ); // Scale the overlay hr = CreateScaledSurfaceCopy( pLoadSurface, d3dxImageInfo.Width, d3dxImageInfo.Height, fxScale, fyScale, &pScaleSurface ); if( FAILED(hr) ) goto LCleanReturn; // Load the stored bitmap from the scaled D3D surface hr = CreateDIBSectionFromSurface( pScaleSurface, &(pOverlay->hImage) ); if( FAILED(hr) ) goto LCleanReturn; SAFE_RELEASE( pScaleSurface ); } hr = DI_OK; LCleanReturn: SAFE_RELEASE( pLoadSurface ); SAFE_RELEASE( pScaleSurface ); return hr; } //----------------------------------------------------------------------------- // Name: GetCustomUISize // Desc: Determine the dimensions of the custom UI based on default values and // user-supplied sizing information. //----------------------------------------------------------------------------- HRESULT CDIDevImage::GetCustomUISize( SIZE* pSize ) { if( pSize == NULL ) return DIERR_INVALIDPARAM; // Calculate view dimensions based on values set during a call to // SetOutputImageSize(), or the default values defined in the header switch( m_dwScaleMethod ) { case DIDISOIS_RESIZE : pSize->cx = m_dwWidthPref; pSize->cy = m_dwHeightPref; break; case DIDISOIS_MAINTAINASPECTUSINGWIDTH : pSize->cx = m_dwWidthPref; pSize->cy = (LONG) ( 0.5 + DIDICONST_CUSTOM_VIEW_HEIGHT * ( (FLOAT)m_dwWidthPref / DIDICONST_CUSTOM_VIEW_WIDTH ) ); break; case DIDISOIS_MAINTAINASPECTUSINGHEIGHT : pSize->cy = m_dwHeightPref; pSize->cx = (LONG) ( 0.5 + DIDICONST_CUSTOM_VIEW_WIDTH * ( (FLOAT)m_dwHeightPref / DIDICONST_CUSTOM_VIEW_HEIGHT ) ); break; default: pSize->cx = DIDICONST_CUSTOM_VIEW_WIDTH; pSize->cy = DIDICONST_CUSTOM_VIEW_HEIGHT; }; return DI_OK; } //----------------------------------------------------------------------------- // Name: BuildCustomUI // Desc: Creates the callout rects for each view based on stored sizing // information and callout strings. //----------------------------------------------------------------------------- HRESULT CDIDevImage::BuildCustomUI() { HDC hdc = NULL; SIZE size = {0}; UINT i = 0; int nMaxNameWidth = 0; int nMaxNameHeight = 0; int nMaxCalloutWidth = 0; int nMaxCalloutHeight = 0; int nRowsPerView = 0; int nColsPerView = 0; int nNumVisObjects = 0; const int GUTTER_SIZE = 20; const int PADDING_SIZE = 20; const int SPACING_WIDTH = 10; const int SPACING_HEIGHT = 10; const int MAX_CHARS_OBJECT = 20; const int MAX_CHARS_ACTION = 20; // we need a device context in order to evaluate the text metrics hdc = CreateDC( TEXT("DISPLAY"), NULL, NULL, NULL ); if( NULL == hdc ) return E_FAIL; // select the font into the dc SelectObject( hdc, m_hFont ); // determine the largest device name for( i=0; i < m_dwNumObjects; i++ ) { if( DIDICOS_INVISIBLE & m_apObject[i]->GetCalloutState() ) continue; nNumVisObjects++; TCHAR str[ MAX_PATH ] = {0}; m_apObject[i]->GetName( str, MAX_PATH ); if( lstrlen(str) > 15 ) lstrcpy( &str[15], TEXT("...") ); if( str && GetTextExtentPoint32( hdc, str, lstrlen( str ), &size ) ) { nMaxNameWidth = max( nMaxNameWidth, size.cx ); nMaxNameHeight = max( nMaxNameHeight, size.cy ); } m_apObject[i]->GetCalloutText( str, MAX_PATH ); if( lstrlen(str) > 15 ) lstrcpy( &str[15], TEXT("...") ); if( str && GetTextExtentPoint32( hdc, str, lstrlen( str ), &size ) ) { nMaxCalloutWidth = max( nMaxCalloutWidth, size.cx ); nMaxCalloutHeight = max( nMaxCalloutHeight, size.cy ); } } // Optionally, you can help constrain the callout sizes by restricting the // string lengths. TEXTMETRIC tm = {0}; if( GetTextMetrics( hdc, &tm ) ) { nMaxCalloutWidth = min( nMaxCalloutWidth, MAX_CHARS_ACTION * tm.tmAveCharWidth ); nMaxNameWidth = min( nMaxNameWidth, MAX_CHARS_OBJECT * tm.tmAveCharWidth ); } // Release resources DeleteDC( hdc ); // Calculate view dimensions GetCustomUISize( &size ); // determine how many callouts we can fit on a single view nColsPerView = ( size.cx - (2 * PADDING_SIZE) + GUTTER_SIZE ) / ( nMaxNameWidth + nMaxCalloutWidth + SPACING_WIDTH + GUTTER_SIZE ); nColsPerView = max( nColsPerView, 1 ); nRowsPerView = ( size.cy - (2 * PADDING_SIZE) ) / ( nMaxNameHeight + SPACING_HEIGHT ); nRowsPerView = max( nRowsPerView, 1 ); m_dwNumViews = nNumVisObjects / ( nColsPerView * nRowsPerView ); m_dwNumViews = max( m_dwNumViews, 1 ); // now all the dimensions are calculated and the callouts can be // allocated. for( i=0; i < m_dwNumObjects; i++ ) { m_apObject[i]->AllocateViews( m_dwNumViews ); } DIDICallout* pCallout = NULL; DIDIOverlay* pOverlay = NULL; int x = 0, y = 0; UINT index = 0; // Build the view by enumerating through the callouts and // For each view... for( UINT view=0; view < m_dwNumViews; view++ ) { x = PADDING_SIZE; // For each column... for( int col=0; col < nColsPerView; col++ ) { y = PADDING_SIZE; // For each row... for( int row=0; row < nRowsPerView; row++ ) { // Eat indices to invisible objects until an object is found or // we run out. while( index < m_dwNumObjects && ( DIDICOS_INVISIBLE & m_apObject[index]->GetCalloutState() ) ) { index++; } // If we're on a valid object, calculate the screen coords if( index < m_dwNumObjects ) { pOverlay = m_apObject[index]->GetOverlay( view ); pCallout = m_apObject[index]->GetCallout( view ); pOverlay->rcScaled.left = x; pOverlay->rcScaled.top = y; pOverlay->rcScaled.right = x + nMaxNameWidth; pOverlay->rcScaled.bottom = y + nMaxNameHeight; pCallout->rcScaled.left = x + nMaxNameWidth + SPACING_WIDTH; pCallout->rcScaled.top = y; pCallout->rcScaled.right = pCallout->rcScaled.left + nMaxCalloutWidth; pCallout->rcScaled.bottom = y + nMaxNameHeight; pCallout->dwTextAlign = DIDAL_LEFTALIGNED | DIDAL_BOTTOMALIGNED; } index++; y += nMaxNameHeight + SPACING_HEIGHT; } x += nMaxNameWidth + nMaxCalloutWidth + SPACING_WIDTH + GUTTER_SIZE; } } // Clear the invalid flag m_bInvalidUI = FALSE; return DI_OK; } //----------------------------------------------------------------------------- // Name: CreateScaledSurfaceCopy // Desc: Creates a new surface and copies the contents from the provided // source surface onto the newly created destination surface. //----------------------------------------------------------------------------- HRESULT CDIDevImage::CreateScaledSurfaceCopy( LPDIRECT3DSURFACE9 pSurfaceSrc, DWORD dwWidthSrc, DWORD dwHeightSrc, FLOAT fxScale, FLOAT fyScale, LPDIRECT3DSURFACE9 *ppSurfaceDest ) { HRESULT hr; RECT rcSrc = {0}, rcDest = {0}; // Verify parameters if( NULL == pSurfaceSrc || NULL == ppSurfaceDest ) return DIERR_INVALIDPARAM; // Calculate creation arguments rcSrc.right = dwWidthSrc-1; rcSrc.bottom = dwHeightSrc-1; ScaleRect( &rcSrc, &rcDest, fxScale, fyScale ); // Create the stored surface *ppSurfaceDest = GetCloneSurface( rcDest.right, rcDest.bottom ); // Since we're using a surface workaround, the d3d functions should only // be called if we're actually scaling the surface. if( EqualRect( &rcSrc, &rcDest ) ) { D3DLOCKED_RECT d3drcSrc = {0}; D3DLOCKED_RECT d3drcDest = {0}; pSurfaceSrc->LockRect( &d3drcSrc, NULL, 0 ); (*ppSurfaceDest)->LockRect( &d3drcDest, NULL, 0 ); BYTE* pBitsSrc = (BYTE*) d3drcSrc.pBits; BYTE* pBitsDest = (BYTE*) d3drcDest.pBits; for( int y = rcDest.top; y < rcDest.bottom; y++ ) { CopyMemory( pBitsDest, pBitsSrc, d3drcDest.Pitch ); pBitsDest += d3drcDest.Pitch; pBitsSrc += d3drcSrc.Pitch; } } else { // Copy the image onto the stored surface hr = D3DXLoadSurfaceFromSurface( *ppSurfaceDest, NULL, &rcDest, pSurfaceSrc, NULL, &rcSrc, D3DX_FILTER_TRIANGLE, NULL ); if( FAILED(hr) ) goto LCleanReturn; } // Everything went OK. Return before cleaning up the new surface. return DI_OK; LCleanReturn: // An error occured. Clean up the new surface before returning. if( *ppSurfaceDest ) { (*ppSurfaceDest)->Release(); *ppSurfaceDest = NULL; } return hr; } //----------------------------------------------------------------------------- // Name: CleanUp // Desc: Release all allocated memory, zero out member variables //----------------------------------------------------------------------------- void CDIDevImage::CleanUp() { UINT i=0; //loop var // Release resources first DestroyImages(); // Call destructors on all stored objects for( i=0; i < m_dwNumObjects; i++ ) { delete m_apObject[i]; } // Delete object array if( m_apObject ) delete [] m_apObject; // Delete BITMAP storage array delete [] m_atszImagePath; delete [] m_ahImages; if( m_hFont ) DeleteObject( m_hFont ); m_apObject = NULL; m_atszImagePath = NULL; m_ahImages = NULL; m_dwNumObjects = 0; m_dwNumViews = 0; m_dwWidthPref = 0; m_dwHeightPref = 0; m_dwScaleMethod = 0; m_hFont = NULL; m_bInitialized = FALSE; m_bCustomUI = FALSE; m_bInvalidUI = TRUE; } //----------------------------------------------------------------------------- // Name: DestroyImages // Desc: Release all stored images //----------------------------------------------------------------------------- VOID CDIDevImage::DestroyImages() { UINT i=0; //loop var // Release all background images if( m_ahImages ) { for( i=0; i < m_dwNumViews; i++ ) { if( m_ahImages[i] ) DeleteObject( m_ahImages[i] ); m_ahImages[i] = NULL; } } // Release all object images for( i=0; i < m_dwNumObjects; i++ ) { if( m_apObject[i] ) m_apObject[i]->DestroyImages(); } }; //----------------------------------------------------------------------------- // Name: CreateFont // Desc: Create the GDI font to use for callout text //----------------------------------------------------------------------------- VOID CDIDevImage::CreateFont() { // Create display font LOGFONT lf; ZeroMemory( &lf, sizeof(LOGFONT) ); lf.lfHeight = 14; lf.lfWeight = 700; lf.lfCharSet = DEFAULT_CHARSET; lf.lfOutPrecision = OUT_TT_ONLY_PRECIS; _tcscpy( lf.lfFaceName, TEXT("arial") ); m_hFont = CreateFontIndirect( &lf ); // Have a backup plan if( NULL == m_hFont ) m_hFont = (HFONT) GetStockObject( DEFAULT_GUI_FONT ); } //----------------------------------------------------------------------------- // Name: CDIDIObject // Desc: Constructor //----------------------------------------------------------------------------- CDIDIObject::CDIDIObject( DWORD dwID, DWORD dwNumViews ) { m_dwID = dwID; m_crNormColor = RGB(150, 150, 200); m_crHighColor = RGB(255, 255, 255); m_dwNumViews = dwNumViews; m_aCallout = NULL; m_aOverlay = NULL; m_dwState = 0; AllocateViews( dwNumViews ); lstrcpy( m_strCallout, TEXT("_ _ _") ); lstrcpy( m_strName, TEXT("") ); }; //----------------------------------------------------------------------------- // Name: ~CDIDIObject // Desc: Destructor //----------------------------------------------------------------------------- CDIDIObject::~CDIDIObject() { DestroyImages(); delete [] m_aCallout; delete [] m_aOverlay; }; //----------------------------------------------------------------------------- // Name: AllocateViews // Desc: The number of views for a custom UI can change, requiring the need // to free and reallocate resources depending on the number of views. //----------------------------------------------------------------------------- HRESULT CDIDIObject::AllocateViews( DWORD dwNumViews ) { // Release current views delete [] m_aCallout; delete [] m_aOverlay; m_dwNumViews = 0; m_aCallout = new DIDICallout[dwNumViews]; if( NULL == m_aCallout ) return DIERR_OUTOFMEMORY; m_aOverlay = new DIDIOverlay[dwNumViews]; if( NULL == m_aOverlay ) { delete[] m_aCallout; m_aCallout = NULL; return DIERR_OUTOFMEMORY; } ZeroMemory( m_aCallout, sizeof(DIDICallout) * dwNumViews ); ZeroMemory( m_aOverlay, sizeof(DIDIOverlay) * dwNumViews ); m_dwNumViews = dwNumViews; return DI_OK; }; //----------------------------------------------------------------------------- // Name: SetOverlay // Desc: Sets the values for an overlay in the object's current list //----------------------------------------------------------------------------- VOID CDIDIObject::SetOverlay( DWORD dwViewID, LPCTSTR strImagePath, RECT rect ) { m_aOverlay[dwViewID].rcInit = rect; m_aOverlay[dwViewID].rcScaled = rect; lstrcpy( m_aOverlay[dwViewID].strImagePath, strImagePath ); } //----------------------------------------------------------------------------- // Name: SetCallout // Desc: Adds the values for a callout in the object's current list //----------------------------------------------------------------------------- VOID CDIDIObject::SetCallout( DWORD dwViewID, DWORD dwNumPoints, POINT *aptLine, RECT rect, DWORD dwTextAlign ) { m_aCallout[dwViewID].dwNumPoints = dwNumPoints; m_aCallout[dwViewID].rcInit = rect; m_aCallout[dwViewID].rcScaled = rect; m_aCallout[dwViewID].dwTextAlign = dwTextAlign; for( int i=0; i < 5; i++) { m_aCallout[dwViewID].aptLineInit[i] = aptLine[i]; m_aCallout[dwViewID].aptLineScaled[i] = aptLine[i]; } } //----------------------------------------------------------------------------- // Name: DestroyImages // Desc: Release all stored images //----------------------------------------------------------------------------- VOID CDIDIObject::DestroyImages() { UINT i=0; //loop var // Release all object images for( i=0; i < m_dwNumViews; i++ ) { if( m_aOverlay[i].hImage ) DeleteObject( m_aOverlay[i].hImage ); m_aOverlay[i].hImage = NULL; } }; //----------------------------------------------------------------------------- // Name: SetCalloutText // Desc: copy as many characters as will fit, leaving room for the terminating // null and 3 elipses. //----------------------------------------------------------------------------- VOID CDIDIObject::SetCalloutText( LPCTSTR strText ) { _tcsncpy( m_strCallout, strText, MAX_PATH - 4 ); } //----------------------------------------------------------------------------- // Name: GetCalloutText // Desc: Retrieve the current callout text from the name buffer up to the // amount of characters specified by dwSize //----------------------------------------------------------------------------- VOID CDIDIObject::GetCalloutText( LPTSTR strText, DWORD dwSize ) { _tcsncpy( strText, m_strCallout, dwSize ); } //----------------------------------------------------------------------------- // Name: ScaleView // Desc: Scale all rects and points to the given scaling factors //----------------------------------------------------------------------------- VOID CDIDIObject::ScaleView( DWORD dwViewID, FLOAT fxScale, FLOAT fyScale ) { UINT i=0; // Overlay/Callout Rects ScaleRect( &(m_aOverlay[dwViewID].rcInit), &(m_aOverlay[dwViewID].rcScaled), fxScale, fyScale ); ScaleRect( &(m_aCallout[dwViewID].rcInit), &(m_aCallout[dwViewID].rcScaled), fxScale, fyScale ); // Callout Lines for( i=0; i < 5; i++ ) { ScalePoint( &(m_aCallout[dwViewID].aptLineInit[i]), &(m_aCallout[dwViewID].aptLineScaled[i]), fxScale, fyScale ); } } //----------------------------------------------------------------------------- // Name: ApplyAlphaChannel // Desc: Restore the alpha information from the source bitmap to the // destination bitmap. GDI uses COLORREF structures which lack an alpha // channel, and draws everything as fully transparent. By using a // separate bitmap to store opacity information, the alpha channel can // be restored. //----------------------------------------------------------------------------- HRESULT ApplyAlphaChannel( HBITMAP hbmpDest, HBITMAP hbmpSrc, BOOL bOpaque ) { HRESULT hr = S_OK; BITMAP bmpInfoSrc = {0}; BITMAP bmpInfoDest = {0}; // Verify parameters if( NULL == hbmpDest || NULL == hbmpSrc ) return DIERR_INVALIDPARAM; // Get the bitmap pixel data if( 0 == GetObject( hbmpSrc, sizeof(BITMAP), &bmpInfoSrc ) ) return E_FAIL; if( 0 == GetObject( hbmpDest, sizeof(BITMAP), &bmpInfoDest ) ) return E_FAIL; // Cast the data pointers DWORD* pBitsSrc = (DWORD*) bmpInfoSrc.bmBits; DWORD* pBitsDest = (DWORD*) bmpInfoDest.bmBits; // Syncronize bitmap pixel info GdiFlush(); // For each pixel in the rect... for( int y=0; y < bmpInfoDest.bmHeight; y++ ) { for( int x=0; x < bmpInfoDest.bmWidth; x++ ) { if( *pBitsSrc ) { // The source bitmap contains information at this pixel, which // may signal full or partial opacity. If the passed bOpaque // flag is set, the method should treat partially opaque pixels // as being fully opaque. This is used when the user has selected // a fully opaque background color and wants the resulting image // to also be fully opaque. *pBitsDest |= bOpaque ? ALPHA_MASK : ( GetBlue( *pBitsSrc ) << 24 ); } // Advance the pixel pointers. pBitsSrc++; pBitsDest++; } } return hr; } //----------------------------------------------------------------------------- // Name: RestoreRect // Desc: Restore the pixel data of an image for the given RECT //----------------------------------------------------------------------------- HRESULT RestoreRect( HBITMAP hbmpDest, CONST RECT* prcDest, LPBYTE pSrcPixels ) { BITMAP bmpInfoDest = {0}; // Verify parameters if( NULL == hbmpDest || NULL == pSrcPixels || NULL == prcDest ) return DIERR_INVALIDPARAM; // Get the bitmap pixel data if( 0 == GetObject( hbmpDest, sizeof(BITMAP), &bmpInfoDest ) ) return E_FAIL; // Cast the data pointers DWORD* pBitsSrc = (DWORD*) pSrcPixels; DWORD* pBitsDest = (DWORD*) bmpInfoDest.bmBits; // Syncronize bitmap pixel info GdiFlush(); // Advance the pixel pointers to the starting position pBitsDest += ( prcDest->top * bmpInfoDest.bmWidth ) + prcDest->left; pBitsSrc += ( prcDest->top * bmpInfoDest.bmWidth ) + prcDest->left; // For each scanline in rcDest... for( int y = prcDest->top; y < prcDest->bottom; y++ ) { CopyMemory( pBitsDest, pBitsSrc, sizeof(DWORD) * ( prcDest->right - prcDest->left ) ); // Advance pointers pBitsDest += bmpInfoDest.bmWidth; pBitsSrc += bmpInfoDest.bmWidth; } return DI_OK; } //----------------------------------------------------------------------------- // Name: ApplyOverlay // Desc: Use manual alpha blending to paste the overlay bitmap on top of the // destination bitmap. //----------------------------------------------------------------------------- HRESULT ApplyOverlay( HBITMAP hbmpDest, CONST RECT* prcDest, HBITMAP hbmpSrc ) { BITMAP bmpInfoSrc = {0}; BITMAP bmpInfoDest = {0}; // Verify parameters if( NULL == hbmpDest || NULL == hbmpSrc || NULL == prcDest ) return DIERR_INVALIDPARAM; // Get the bitmap pixel data if( 0 == GetObject( hbmpSrc, sizeof(BITMAP), &bmpInfoSrc ) ) return E_FAIL; if( 0 == GetObject( hbmpDest, sizeof(BITMAP), &bmpInfoDest ) ) return E_FAIL; // Cast the data pointers DWORD* pBitsSrc = (DWORD*) bmpInfoSrc.bmBits; DWORD* pBitsDest = (DWORD*) bmpInfoDest.bmBits; // Syncronize bitmap pixel info GdiFlush(); // Advance the destination pixel to the starting position pBitsDest += ( prcDest->top * bmpInfoDest.bmWidth ) + prcDest->left; // For each pixel... for( int y=0; y < bmpInfoSrc.bmHeight; y++ ) { for( int x=0; x < bmpInfoSrc.bmWidth; x++ ) { // calculate src and dest alpha if( ( *pBitsSrc & ALPHA_MASK ) == ALPHA_MASK ) { // Source pixel is completely opaque, have it replace the // current destination pixel *pBitsDest = *pBitsSrc; } else if( ( *pBitsSrc & ALPHA_MASK ) == 0 ) { // Source pixel is completely transparent, do nothing } else { // This formula computes the blended component value: // ( ALPHA * ( srcPixel - destPixel ) ) / 256 + destPixel DWORD dwMultiplier = GetAlpha( *pBitsSrc ); // Decompose the image into color components DWORD dwRed = GetRed( *pBitsDest ); DWORD dwGreen = GetGreen( *pBitsDest ); DWORD dwBlue = GetBlue( *pBitsDest ); // Calculate the component blend dwRed = ( dwMultiplier * ( GetRed( *pBitsSrc ) - dwRed ) ) / 256 + dwRed; dwGreen = ( dwMultiplier * ( GetGreen( *pBitsSrc ) - dwGreen ) ) / 256 + dwGreen; dwBlue = ( dwMultiplier * ( GetBlue( *pBitsSrc ) - dwBlue ) ) / 256 + dwBlue; // Compose the blended pixel. The destination bitmap's original alpha // value is preserved. *pBitsDest = (*pBitsDest & ALPHA_MASK) | dwRed << 16 | dwGreen << 8 | dwBlue; } // Move to next pixel pBitsDest++; pBitsSrc++; } // Advance destination pointer pBitsDest += ( bmpInfoDest.bmWidth - bmpInfoSrc.bmWidth ); } return DI_OK; } //----------------------------------------------------------------------------- // Name: FillBackground // Desc: Fills the background of the given bitmap with the provided fill color. // The background of the image is defined by the alpha channel. //----------------------------------------------------------------------------- HRESULT FillBackground( HBITMAP hbmpDest, D3DCOLOR Fill ) { BITMAP bmpInfo = {0}; // Verify parameters if( NULL == hbmpDest ) return DIERR_INVALIDPARAM; // Get the bitmap pixel data if( 0 == GetObject( hbmpDest, sizeof(BITMAP), &bmpInfo ) ) return E_FAIL; // Cast the data pointers DWORD* pBits = (DWORD*) bmpInfo.bmBits; // Extract the component channels of the background color. DWORD dwBkAlpha = GetAlpha( Fill ); DWORD dwBkRed = GetRed( Fill ); DWORD dwBkGreen = GetGreen( Fill ); DWORD dwBkBlue = GetBlue( Fill ); // If the background color is defined as being completely opaque, the fill // method is performed a little differently. Normally, the per-pixel alpha // value is blended along with the color components, but this can result in // areas of the image which are partially transparent. If the provided fill // color is opaque, it's assumed that the user wishes to have the final // rendered surface completely opaque. If the provided background color is // partially transparent, then the alpha channel is blended to allow for // an antialiased border around the device image. BOOL bBkOpaque = ( dwBkAlpha == 255 ); // Syncronize bitmap pixel info GdiFlush(); // For each pixel in the rect... for( int y=0; y < bmpInfo.bmHeight; y++ ) { for( int x=0; x < bmpInfo.bmWidth; x++ ) { if( (*pBits & ALPHA_MASK) == ALPHA_MASK ) { // Destination pixel is completely opaque, no fill needed. } else if( (*pBits & ALPHA_MASK) == 0 ) { // Destination pixel is completely transparent, replace with // the fill color. *pBits = Fill; } else { // This formula computes the blended component value: // ( ALPHA * ( srcPixel - destPixel ) ) / 256 + destPixel DWORD dwMultiplier = GetAlpha( *pBits ); // Calculate the component blend DWORD dwAlpha = bBkOpaque ? 255 : ( dwMultiplier * ( GetAlpha( *pBits ) - dwBkAlpha ) ) / 256 + dwBkAlpha; DWORD dwRed = ( dwMultiplier * ( GetRed( *pBits ) - dwBkRed ) ) / 256 + dwBkRed; DWORD dwGreen = ( dwMultiplier * ( GetGreen( *pBits ) - dwBkGreen ) ) / 256 + dwBkGreen; DWORD dwBlue = ( dwMultiplier * ( GetBlue( *pBits ) - dwBkBlue ) ) / 256 + dwBkBlue; // Compose the final pixel color value *pBits = dwAlpha << 24 | dwRed << 16 | dwGreen << 8 | dwBlue; } // Move to next pixel pBits++; } } return DI_OK; } //----------------------------------------------------------------------------- // Name: DrawTooltip // Desc: Draws tooltip text on the provided device contexts. The tooltip string // is drawn in relation to the RECT containing the truncated text, and // using the given foreground, background, and border colors. //----------------------------------------------------------------------------- HRESULT DrawTooltip( HDC hdcRender, HDC hdcAlpha, TCHAR* strTooltip, RECT* prcBitmap, RECT* prcTruncated, COLORREF crFore, COLORREF crBack, COLORREF crBorder ) { // Create the tooltip font. This should be highly readable at a small point // size, so a raster font has been chosen. HFONT hTipFont = NULL; LOGFONT lfTipFont = {0}; SIZE sizeText = {0}; lstrcpy( lfTipFont.lfFaceName, TEXT("MS Sans Serif") ); lfTipFont.lfHeight = 8; lfTipFont.lfOutPrecision = OUT_RASTER_PRECIS; lfTipFont.lfQuality = PROOF_QUALITY; hTipFont = CreateFontIndirect( &lfTipFont ); if( !hTipFont ) hTipFont = (HFONT) GetStockObject( SYSTEM_FONT ); HFONT hOldRenderFont = (HFONT) SelectObject( hdcRender, hTipFont ); HFONT hOldAlphaFont = (HFONT) SelectObject( hdcAlpha, hTipFont ); // How much screen space are we going to need? GetTextExtentPoint32( hdcRender, strTooltip, lstrlen( strTooltip ), &sizeText ); // Position the rect right above the callout space RECT rcTooltip = { 0, 0, sizeText.cx, sizeText.cy }; OffsetRect( &rcTooltip, prcTruncated->left + 2, prcTruncated->top - sizeText.cy - 2); InflateRect( &rcTooltip, 2, 2 ); // Adjust the tooltip rect if it's beyond the screen edge if( rcTooltip.top < 0 ) OffsetRect( &rcTooltip, 0, -rcTooltip.top ); if( rcTooltip.right > prcBitmap->right ) OffsetRect( &rcTooltip, prcBitmap->right - rcTooltip.right, 0 ); // Draw tooltip HBRUSH hOldRenderBrush = (HBRUSH) SelectObject( hdcRender, CreateSolidBrush( crBack ) ); HPEN hOldRenderPen = (HPEN) SelectObject( hdcRender, CreatePen( PS_SOLID, 1, crBorder ) ); COLORREF crOldRenderTextColor = SetTextColor( hdcRender, crFore ); Rectangle( hdcRender, rcTooltip.left, rcTooltip.top, rcTooltip.right, rcTooltip.bottom ); FillRect( hdcAlpha, &rcTooltip, (HBRUSH) GetStockObject( WHITE_BRUSH ) ); InflateRect( &rcTooltip, -2, -2 ); DrawText( hdcRender, strTooltip, lstrlen( strTooltip ), &rcTooltip, 0 ); // Restore the DC state SelectObject( hdcRender, hOldRenderFont ); SelectObject( hdcAlpha, hOldAlphaFont ); DeleteObject( (HBRUSH) SelectObject( hdcRender, hOldRenderBrush ) ); DeleteObject( (HPEN) SelectObject( hdcRender, hOldRenderPen ) ); SetTextColor( hdcRender, crOldRenderTextColor ); DeleteObject( hTipFont ); return DI_OK; } //----------------------------------------------------------------------------- // Name: CreateDIBSectionFromSurface // Desc: Extract the pixel information from the provided surface and create // a new DIB Section. //----------------------------------------------------------------------------- HRESULT CreateDIBSectionFromSurface( LPDIRECT3DSURFACE9 pSurface, HBITMAP* phBitmap, SIZE* pSize ) { HRESULT hr; D3DSURFACE_DESC d3dDesc; D3DLOCKED_RECT d3dRect; LPBYTE pDIBBits; LPBYTE pSurfBits; BITMAPINFO bmi = {0}; // Get the surface info hr = pSurface->GetDesc( &d3dDesc ); if( FAILED( hr ) ) return hr; // Lock the surface hr = pSurface->LockRect( &d3dRect, NULL, 0 ); if( FAILED( hr ) ) return hr; pSurfBits = (LPBYTE) d3dRect.pBits; // Fill in the bitmap creation info bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = pSize ? pSize->cx : d3dDesc.Width; bmi.bmiHeader.biHeight = pSize ? -pSize->cy : ( - (int) d3dDesc.Height ); // Top-down DIB bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; HDC hdcMem = CreateCompatibleDC( NULL ); if( NULL == hdcMem ) return DIERR_OUTOFMEMORY; // Create the DIBSection *phBitmap = CreateDIBSection( hdcMem, &bmi, DIB_RGB_COLORS,( LPVOID* )&pDIBBits, NULL, 0 ); DeleteDC( hdcMem ); if( NULL == *phBitmap ) return DIERR_OUTOFMEMORY; // Copy the bits for( UINT y=0; y < d3dDesc.Height; y++ ) { CopyMemory( pDIBBits, pSurfBits, ( d3dDesc.Width * sizeof(DWORD) ) ); pDIBBits += bmi.bmiHeader.biWidth * sizeof(DWORD); pSurfBits += d3dRect.Pitch; } return DI_OK; } //----------------------------------------------------------------------------- // Name: EnumDeviceObjectsCB // Desc: Cycle through device objects, adding information to the passed // CDIDevImage object when appropriate. //----------------------------------------------------------------------------- BOOL CALLBACK EnumDeviceObjectsCB( LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef ) { HRESULT hr; // Extract the passed pointer CDIDevImage *pDIDevImage = (CDIDevImage*) pvRef; CDIDIObject *pObject = NULL; // Add the object to the list hr = pDIDevImage->AddObject( lpddoi->dwType ); if( FAILED(hr) ) return DIENUM_STOP; // Set the object's friendly name pObject = pDIDevImage->GetObject( lpddoi->dwType ); if( pObject ) pObject->SetName( lpddoi->tszName ); return DIENUM_CONTINUE; } //----------------------------------------------------------------------------- // Name: DidcvDirect3DSurface9Clone // Desc: a clone surface //----------------------------------------------------------------------------- class DidcvDirect3DSurface9Clone : public IUnknown { private: int m_iRefCount; BYTE *m_pData; D3DSURFACE_DESC m_Desc; public: DidcvDirect3DSurface9Clone() : m_pData( NULL ), m_iRefCount( 1 ) { } ~DidcvDirect3DSurface9Clone() { delete[] m_pData; } public: // IUnknown methods STDMETHOD( QueryInterface )( REFIID riid, VOID **ppvObj ) { return E_NOINTERFACE; } STDMETHOD_( ULONG,AddRef )() { return ++m_iRefCount; } STDMETHOD_( ULONG,Release )() { if( !--m_iRefCount ) { delete this; return 0; } return m_iRefCount; } // IDirect3DResource9 methods STDMETHOD( GetDevice )( IDirect3DDevice9 **ppDevice ) { return E_FAIL; } STDMETHOD( SetPrivateData )( REFGUID riid, CONST VOID *pvData, DWORD cbData, DWORD dwFlags ) { return E_FAIL; } STDMETHOD( GetPrivateData )( REFGUID riid, VOID* pvData, DWORD *pcbData ) { return E_FAIL; } STDMETHOD( FreePrivateData )( REFGUID riid ) { return E_FAIL; } STDMETHOD( SetPriority )( DWORD PriorityNew ) { return E_FAIL; } STDMETHOD_( DWORD, GetPriority )() { return 0; } STDMETHOD( PreLoad )() { return E_FAIL; } STDMETHOD( GetType )() { return E_FAIL; } // IDirect3DSurface9 methods STDMETHOD( GetContainer )( REFIID riid, void **ppContainer ) { return E_FAIL; } STDMETHOD_( D3DSURFACE_DESC, GetDesc )() { return m_Desc; } STDMETHOD( LockRect )( D3DLOCKED_RECT* pLockedRect, CONST RECT* pRect, DWORD dwFlags ) { // Assume the entire surface is being locked. pLockedRect->Pitch = m_Desc.Width * 4; pLockedRect->pBits = m_pData; return S_OK; } STDMETHOD( UnlockRect )() { return S_OK; } STDMETHOD( GetDC )() { return E_FAIL; } STDMETHOD( ReleaseDC )() { return E_FAIL; } BOOL Create( int iWidth, int iHeight ) { m_pData = new BYTE[iWidth * iHeight * 4]; if( !m_pData ) return FALSE; m_Desc.Format = D3DFMT_A8R8G8B8; m_Desc.Type = D3DRTYPE_SURFACE; m_Desc.Usage = 0; m_Desc.Pool = D3DPOOL_SYSTEMMEM; m_Desc.MultiSampleType = D3DMULTISAMPLE_NONE; m_Desc.MultiSampleQuality = 0; m_Desc.Width = iWidth; m_Desc.Height = iHeight; return TRUE; } }; //----------------------------------------------------------------------------- // Name: GetCloneSurface // Desc: Fake a Direct3D surface so we don't have to actually create a direct3d // device object. //----------------------------------------------------------------------------- IDirect3DSurface9* GetCloneSurface( int iWidth, int iHeight ) { DidcvDirect3DSurface9Clone *pSurf = new DidcvDirect3DSurface9Clone; if( !pSurf ) return NULL; if( !pSurf->Create( iWidth, iHeight ) ) { delete pSurf; return NULL; } return( IDirect3DSurface9* )pSurf; }