|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Here is an example of how to extend the
IntroductionThis article is about a BackgroundI wrote this code because I needed a way of displaying a background image that contains multiple smaller images on it. I also needed a quick way of adding and removing images from the display. Being able to get more information about a particular image while the mouse was over it, was a must. That is why I used a technique to create a non-transparent region, so that only the visible portion of the image will trigger the tool tip description. New Features (Added for Version 2.01)
Existing FeaturesAdded for Version 2.0
From Initial Release
CLayerInfo ClassThe Construction/Destructionpublic: CLayerInfo(); CLayerInfo( const CLayerInfo &src ); // Copy constructor // Destructor virtual ~CLayerInfo(); Implementationpublic: // Copies the source layer object into this layer object. CLayerInfo &Copy( const CLayerInfo &src ); // Assigns the contents of one layer object to another. CLayerInfo &operator=( const CLayerInfo &src ); // Compares the layer index of the source layer object with the // layer index of this layer object. These comparison functions // are used by the vector for sorting purposes. bool operator==( const CLayerInfo &layerInfo ) const; bool operator<( const CLayerInfo &layerInfo ) const; bool operator>( const CLayerInfo &layerInfo ) const; // Here are some static bitmap utility functions // that can be used independently of the layers. // Copies the source bitmap into the destination bitmap. static bool CopyBitmap( const CBitmap &bmpSrc, CBitmap &bmpDest ); // Copies a section from an existing source bitmap // into a new destination bitmap. static bool CopyBitmapSection( CBitmap &bmpSrc, CBitmap &bmpDest, CRect &rectSection, CDC *pDC = NULL ); // Allocates and initializes a BITMAPINFO structure. static BITMAPINFO *PrepareRGBBitmapInfo( int nWidth, int nHeight ); // Gets the color from a specific location within a bitmap. static COLORREF GetColorFromBitmap( const CPoint &ptLocation, CBitmap &bitmap ); Attributespublic: // Unique layer identifier, so that the layer // can be found within the vector. int m_nLayerID; // Indicates which level this layer is at. // The vector is sorted by this index. (0 = bottom layer). int m_nLayerIndex; // This is the description that // will be displayed in the tool tip. CString m_strLayerDesc; // Indicates whether or not this // layer will be displayed or hidden. bool m_bVisible; // Indicates that this layer contains a region // which is used to determine if // the mouse is within the layer. bool m_bUseRgn; // Indicates that this layer can be moved with the mouse. bool m_bTrackingEnabled; // Indicates that this layer is currently being tracked. bool m_bTracking; // Indicates that this layer will be displayed above // all visible layers while being tracked. // This allows the tracked layer to be displayed // faster instead of having to redraw each section // of the above layers that intersect with the tracked layer. bool m_bShowOnTopWhileTracking; // Indicates the point within the layer // that the left mouse button was first pressed. CPoint m_ptTrackingStart; // Indicates that this layer contains transparent pixels. bool m_bTransparent; // Indicates the transparent color. COLORREF m_colTransparent; // Instead of setting the transparent // color manually, you can specify the location // within the bitmap to get the transparent color. CPoint m_ptTransparentPixel; // Specifies where you want to put this layer // on the CLayeredBitmapCtrl window. CPoint m_ptLocation; // Indicates whether or not this layer will display // a focus rectangle while the // left mouse button is pressed. bool m_bFocusRectangleEnabled; // Indicates that the focus rectangle should be displayed. bool m_bShowFocusRectangle; // Indicates the color of the focus rectangle. COLORREF m_colFocusRectangle; // This is the bitmap for the current layer. CBitmap m_bmp; // This is the region for the current layer. // (The region does not have to be created). CRgn m_rgn; CLayeredBitmapCtrl ClassThe Construction/Destructionpublic: // Default CStatic contructor. CLayeredBitmapCtrl(); // Destructor - Frees all of the memory associated with the layers. virtual ~CLayeredBitmapCtrl(); Attributespublic: // This is the bitmap that is displayed on the screen within the control. CBitmap m_bmpCombined; protected: // This vector contains all of the layer // objects even if they are not displayed. vector<CLayerInfo> m_vecLayerInfo; // Indicates if tool tips are to be shown // if the mouse is within a layer's region. bool m_bShowToolTips; // Indicates that the tool tip control has been initialized. bool m_bToolTipsInitialized; // Indicates that tool tips will be hidden // while moving a layer with the mouse. bool m_bHideTrackingToolTip; // String that is displayed in the tool tip. CString m_strToolTip; // Tool tip control that is displayed // if the mouse is within a layer's region. CToolTipCtrl m_toolTip; // Indicates if the parent of this // control is a dialog or a view type window. // This way we can determine which system // color to use (COLOR_BTNFACE = CDialog, // COLOR_WINDOW = CView). bool m_bIsParentDlg; // Indicates if the system color // is used for the background color of the control. bool m_bUseSysColor; // Indicates the control's background color. COLORREF m_colCtrlBG; Public Methods
Using the codeTo use this control in your application, add the LayeredBitmapCtrl.h and LayeredBitmapCtrl.cpp files to your project. Use the Resource Editor to add a Picture Control to your dialog. Edit the properties of the Picture Control and rename the control from There are two ways to add a variable for the control.
Don't forget to add the following to your dialog's header file: #include "LayeredBitmapCtrl.h" Make sure that you have a unique layer ID for each layer. For the demo project, I created an enumerated list so that I knew each ID would be unique. I setup the layers within a function that I call at the end of the The How to setup layersThis is the method I used in the demo to setup the various layers. For the first layer, I don't specify a transparent color or create a region. The location of the bitmap is defaulted to the upper left-hand corner of the control. Since this layer is the same size as the control, there is no need to change its location. CLayerInfo *pLayerInfo = NULL;
// Here are the background bitmaps for the Layer1 Selection.
// Create a new layer.
pLayerInfo = new CLayerInfo();
pLayerInfo->m_nLayerID = VALLEY_OF_FIRE_LAYER;
pLayerInfo->m_strLayerDesc = _T("Valley of Fire");
pLayerInfo->m_bmp.LoadBitmap( IDB_VALLEY_OF_FIRE_BITMAP );
m_layeredDisplay.AddLayer( *pLayerInfo );
delete pLayerInfo;
Here is an example of a transparent layer with a region: // Here is the transparent bitmap for the Layer2 Selection. // Create a new layer. pLayerInfo = new CLayerInfo(); pLayerInfo->m_nLayerID = CHAMELEON_BOB_LAYER; pLayerInfo->m_strLayerDesc = _T("Chameleon Bob"); pLayerInfo->m_bTransparent = true; pLayerInfo->m_ptTransparentPixel.x = 1; pLayerInfo->m_ptTransparentPixel.y = 1; pLayerInfo->m_ptLocation.x = 200; pLayerInfo->m_ptLocation.y = 210; pLayerInfo->m_bTrackingEnabled = true; pLayerInfo->m_bFocusRectangleEnabled = true; pLayerInfo->m_colFocusRectangle = RGB( 0, 255, 0 ); pLayerInfo->m_bmp.LoadBitmap( IDB_CHAMELEON_BOB_BITMAP ); m_layeredDisplay.CreateNonTransparentRgn( pLayerInfo ); pLayerInfo->m_rgn.OffsetRgn( pLayerInfo->m_ptLocation.x, pLayerInfo->m_ptLocation.y ); m_layeredDisplay.AddLayer( *pLayerInfo ); delete pLayerInfo; Here is an example of a transparent bitmap. I use magenta For example
Chameleon BobIf you want to have a layer displayed, add the following code before you call the pLayerInfo->m_bVisible = true; Or, you can specify the visibility after the layer has been added, by calling the m_layeredDisplay.SetLayerVisibility( CHAMELEON_BOB_LAYER, true );
Once you have setup the layers, call the m_layeredDisplay.ShowVisibleLayers(); Points of InterestI had looked at various examples of transparent bitmaps and read a section from the Windows 2000 Graphics API Black Book, by Damon Chandler, Michael Fotsch, and eventually, I came up with a solution to my problem. It took a lot of tweaking to get it right, due to the various sizes of the bitmaps. To allow for the focus rectangle and the ability to move layers, I came up with the idea of being able to draw a layer on any bitmap instead of just the combined bitmap. Here is the code used to combine the visible layers onto one bitmap, as well as the code for drawing a layer on to any bitmap: //******************************************************************* // FUNCTION: - CombineLayers // RETURNS: - true - if layers exist. // false - if there are no layers. // PARAMETERS: - // COMMENTS: - Combines all of the visible layer into one bitmap // the will be displayed within the OnPaint function. //******************************************************************* bool CLayeredBitmapCtrl::CombineLayers() { CDC *pDC = NULL; CRect rectCtrl; CSize sizeCtrl, sizeLayer; int nIndex = 0; CLayerInfo *pCurrentLayer = NULL; // Get a pointer to this control's device context. pDC = GetDC(); // Get the client rect from this static control. GetClientRect( &rectCtrl ); // Set the sizeCtrl equal to the width and height // of the static control's client area. sizeCtrl = CSize::CSize( rectCtrl.Width(), rectCtrl.Height() ); // Make sure that the combined bitmap is empty. m_bmpCombined.DeleteObject(); // Create the bitmap that will be contain all of the visible layers. m_bmpCombined.CreateCompatibleBitmap( pDC, sizeCtrl.cx, sizeCtrl.cy ); m_bmpCombined.SetBitmapDimension( sizeCtrl.cx, sizeCtrl.cy ); // Set the background color of the bitmap. CDC tmpDC; CBitmap *pOldBmp = NULL; tmpDC.CreateCompatibleDC( pDC ); pOldBmp = tmpDC.SelectObject( &m_bmpCombined ); // Fill the bitmap with the background color. tmpDC.FillSolidRect( 0, 0, rectCtrl.Width(), rectCtrl.Height(), m_colCtrlBG ); // Cleanup. tmpDC.SelectObject( pOldBmp ); if ( !m_vecLayerInfo.empty() ) { // Loop through each of the layers. for ( nIndex = 0; nIndex < m_vecLayerInfo.size(); nIndex++ ) { pCurrentLayer = reinterpret_cast<CLayerInfo *>(&m_vecLayerInfo[nIndex]); // Only visible layers will be added to the combined bitmap. if ( pCurrentLayer->m_bVisible ) { DrawLayerOnBitmap( &m_bmpCombined, pCurrentLayer ); } } } // Final cleanup. ReleaseDC( pDC ); return true; } //******************************************************************* // FUNCTION: - DrawLayerOnBitmap // RETURNS: - true - if the layer is drawn // false - if either parameter is NULL. // PARAMETERS: - pBmpBackground - An existing bitmap. // pLayerInfo - Pointer to the layer object to be drawn. // COMMENTS: - Draws the specified layer onto an existing bitmap. //******************************************************************* bool CLayeredBitmapCtrl::DrawLayerOnBitmap( CBitmap *pBmpBackground, CLayerInfo *pLayerInfo ) { CDC *pDC = NULL; CDC srcDC, destDC, maskDC, compositeDC, overlayDC; CRect rectCtrl; CSize sizeCtrl, sizeLayer; CBitmap *pOldDestBmp = NULL, *pOldSrcBmp = NULL; CBitmap bmpMask, *pOldMaskBmp = NULL; CBitmap bmpComposite, *pOldCompositeBmp = NULL; CBitmap bmpOverlay, *pOldOverlayBmp = NULL; COLORREF colOld; CPalette *pPalette = NULL; if ( (NULL == pBmpBackground) || (NULL == pLayerInfo) ) { return false; } // Don't try to add the layer's bitmap if it doesn't exist. if ( NULL == pLayerInfo->m_bmp.GetSafeHandle() ) { return false; } // Get a pointer to this control's device context. pDC = GetDC(); // Create some device contexts for bitmap manipulation. // This DC will contain the original bitmap from each layer object. srcDC.CreateCompatibleDC( NULL ); // This DC will contain all of the visible layers. destDC.CreateCompatibleDC( NULL ); // These DCs will be used to mask out the transparent color. maskDC.CreateCompatibleDC( NULL ); compositeDC.CreateCompatibleDC( NULL ); overlayDC.CreateCompatibleDC( NULL ); // Get the client rect from this static control. GetClientRect( &rectCtrl ); // Set the sizeCtrl equal to the width // and height of the static control's client area. sizeCtrl = CSize::CSize( rectCtrl.Width(), rectCtrl.Height() ); // Select the combined bitmap into the destination device context. pOldDestBmp = destDC.SelectObject( pBmpBackground ); // Select the current layer's bitmap into the source device context. pOldSrcBmp = srcDC.SelectObject( &(pLayerInfo->m_bmp) ); // Get the size of the layer's bitmap. sizeLayer = pLayerInfo->m_bmp.GetBitmapDimension(); // Determine if this layer contains a transparent color. if ( pLayerInfo->m_bTransparent ) { // If a transparent pixel location is specified, // get the transparent color from that location. if ( -1 != pLayerInfo->m_ptTransparentPixel.x ) { // Get the transparent color from // the bitmap at the specified location. pLayerInfo->m_colTransparent = srcDC.GetPixel( pLayerInfo->m_ptTransparentPixel ); } // Create the bitmap mask, (black and white). bmpMask.CreateBitmap( sizeCtrl.cx, sizeCtrl.cy, 1, 1, NULL ); // The overlay and composite bitmaps will be compatible with the // destination device context (combined bitmap). bmpOverlay.CreateCompatibleBitmap( &destDC, sizeCtrl.cx, sizeCtrl.cy ); bmpComposite.CreateCompatibleBitmap( &destDC, sizeCtrl.cx, sizeCtrl.cy ); // Select the bitmaps into the appropriate device context. pOldMaskBmp = maskDC.SelectObject( &bmpMask ); pOldOverlayBmp = overlayDC.SelectObject( &bmpOverlay ); pOldCompositeBmp = compositeDC.SelectObject( &bmpComposite ); // Set the background color to the transparent // color for the source layer's bitmap. colOld = srcDC.SetBkColor( pLayerInfo->m_colTransparent ); // Setting the stretch blt mode to COLORONCOLOR // removes the transparent lines of pixels. maskDC.SetStretchBltMode( COLORONCOLOR ); // Copy the layer's inverted bitmap to the mask device // context at the specified location. // By specifying a location the bitmap doesn't // always have to start in the upper left-hand // corner of the static control. maskDC.StretchBlt( pLayerInfo->m_ptLocation.x, pLayerInfo->m_ptLocation.y, sizeLayer.cx, sizeLayer.cy, &srcDC, 0, 0, sizeLayer.cx, sizeLayer.cy, NOTSRCCOPY ); // Set the background color back to the original // color for the mask device context. maskDC.SetBkColor( colOld ); // Copy the inverted bitmap mask onto the overlay bitmap. overlayDC.BitBlt( 0, 0, sizeCtrl.cx, sizeCtrl.cy, &maskDC, 0, 0, NOTSRCCOPY ); // Copy the combined bitmap onto the overlay bitmap. overlayDC.BitBlt( 0, 0, sizeCtrl.cx, sizeCtrl.cy, &destDC, 0, 0, SRCAND ); // Copy the bitmap mask onto the composite bitmap. compositeDC.BitBlt( 0, 0, sizeCtrl.cx, sizeCtrl.cy, &maskDC, 0, 0, SRCCOPY ); // Cleanup bitmap mask. maskDC.SelectObject( pOldMaskBmp ); bmpMask.DeleteObject(); // Select the palette from the destination device context. pPalette = destDC.GetCurrentPalette(); // Does the palette exist? if ( pPalette ) { // Select the palette into the composite device context. pPalette = compositeDC.SelectPalette( pPalette, FALSE ); compositeDC.RealizePalette(); } // AND the layer's bitmap with the composite bitmap. compositeDC.SetStretchBltMode( COLORONCOLOR ); compositeDC.StretchBlt( pLayerInfo->m_ptLocation.x, pLayerInfo->m_ptLocation.y, sizeLayer.cx, sizeLayer.cy, &srcDC, 0, 0, sizeLayer.cx, sizeLayer.cy, SRCAND ); // OR the overlay bitmap with the composite bitmap. compositeDC.BitBlt( 0, 0, sizeCtrl.cx, sizeCtrl.cy, &overlayDC, 0, 0, SRCPAINT ); // Cleanup the overlay bitmap. overlayDC.SelectObject( pOldOverlayBmp ); bmpOverlay.DeleteObject(); // Copy the composite bitmap onto the combined bitmap. destDC.BitBlt( 0, 0, sizeCtrl.cx, sizeCtrl.cy, &compositeDC, 0, 0, SRCCOPY ); // Cleanup the composite bitmap. compositeDC.SelectPalette( pPalette, FALSE ); compositeDC.SelectObject( pOldCompositeBmp ); bmpComposite.DeleteObject(); } else { // Paint this layer's bitmap onto the combined // bitmap at the specified location. destDC.BitBlt( pLayerInfo->m_ptLocation.x, pLayerInfo->m_ptLocation.y, sizeCtrl.cx, sizeCtrl.cy, &srcDC, 0, 0, SRCCOPY ); } // Put the old source bitmap back into the source device context. srcDC.SelectObject( pOldSrcBmp ); // Final cleanup. destDC.SelectObject( pOldDestBmp ); ReleaseDC( pDC ); return true; } Here is the //******************************************************************* // FUNCTION: - OnPaint // RETURNS: - // PARAMETERS: - // COMMENTS: - This function paints the bitmap containing all of // the visible layers onto the static control. //******************************************************************* void CLayeredBitmapCtrl::OnPaint() { CPaintDC dc( this ); // device context for painting CDC displayDC; CRect rectCtrl; CBitmap *pOldBmp = NULL; CBitmap bmpTemp; bool bShowTopLayers = false; int nIndex = 0; CLayerInfo *pCurrentLayer = NULL; CLayerInfo *pTrackedLayer = NULL; CLayerInfo *pTmpLayer = NULL; CRect rectIntersect; // First make a copy of the combined bitmap. CLayerInfo::CopyBitmap( m_bmpCombined, bmpTemp ); // Get the client rect of the static control. GetClientRect( &rectCtrl ); // Create a compatible device context to display the combined bitmap. displayDC.CreateCompatibleDC( &dc ); // Cycle thru the layers to see if we are tracking. for ( nIndex = 0; nIndex < m_vecLayerInfo.size(); nIndex++ ) { pCurrentLayer = reinterpret_cast<CLayerInfo *>(&m_vecLayerInfo[nIndex]); // If we are tracking a layer, then all the visible // layers above may need to be redrawn onto the bitmap, // unless the m_bShowOnTopWhileTracking flag has been set. // Note: The redrawing process is going to get slower // if there are a lot of layers above the // layer that is being tracked. if ( bShowTopLayers ) { if ( pCurrentLayer->m_bVisible ) { rectIntersect.SetRectEmpty(); if ( NULL != pTrackedLayer ) { // Only redraw the current layer if the layer // is within the tracked layer's rectangle. if ( DoLayersIntersect( pTrackedLayer, pCurrentLayer, &rectIntersect ) ) { // Create a temporary layer that will // only contain the portion of the current layer // that needs to be updated. pTmpLayer = new CLayerInfo( *pCurrentLayer ); // Set the transparent pixel location to -1 because the bitmap // section that we will be creating may not have the // same coordinates. Since the original layer object // is copied into the temporary layer, the transparent // color will be set appropriately. pTmpLayer->m_ptTransparentPixel.x = -1; pTmpLayer->m_ptTransparentPixel.y = -1; // Set the location of the temporary layer. pTmpLayer->m_ptLocation.x = rectIntersect.left; pTmpLayer->m_ptLocation.y = rectIntersect.top; int nTmpWidth = rectIntersect.Width(); int nTmpHeight = rectIntersect.Height(); // Get the sizes of the tracked layer and the current layer. CSize sizeTracked = pTrackedLayer->m_bmp.GetBitmapDimension(); CSize sizeCurrent = pCurrentLayer->m_bmp.GetBitmapDimension(); // Tracked layer is on the right // portion of the current layer. if ( pTrackedLayer->m_ptLocation.x >= pCurrentLayer->m_ptLocation.x ) { // Is the width of the tracked layer // smaller than the width of the current layer? if ( sizeTracked.cx < sizeCurrent.cx ) { // Is the width of the current layer larger // than the width of the intersecting rectangle? if ( sizeCurrent.cx > nTmpWidth ) { // We need to get the bitmap section // from the left side of the current bitmap. rectIntersect.left = rectIntersect.left - pCurrentLayer->m_ptLocation.x; } else { // We need to get the bitmap section // from the right side of the current bitmap. rectIntersect.left = sizeCurrent.cx - nTmpWidth; } } else { // We need to get the bitmap section // from the right side of the current bitmap. rectIntersect.left = sizeCurrent.cx - nTmpWidth; } } else { // If the tracked layer is on the left side // of the current layer, // then start the rectangle at zero (0). rectIntersect.left = 0; } // Calculate the right side of the rectangle. rectIntersect.right = rectIntersect.left + nTmpWidth; // Tracked layer is on the bottom // portion of the current layer. if ( pTrackedLayer->m_ptLocation.y >= pCurrentLayer->m_ptLocation.y ) { // Is the height of the tracked layer // smaller than the height of the current layer? if ( sizeTracked.cy < sizeCurrent.cy ) { // Is the height of the current layer larger // than the height of the intersecting rectangle? if ( sizeCurrent.cy > nTmpHeight ) { // We need to get the bitmap section // from the top of the current bitmap. rectIntersect.top = rectIntersect.top - pCurrentLayer->m_ptLocation.y; } else { // We need to get the bitmap section // from the bottom of the current bitmap. rectIntersect.top = sizeCurrent.cy - nTmpHeight; } } else { // We need to get the bitmap section // from the bottom of the current bitmap. rectIntersect.top = sizeCurrent.cy - nTmpHeight; } } else { // If the tracked layer is on the top // of the current layer, // then start the rectangle at zero (0). rectIntersect.top = 0; } // Calculate the bottom of the rectangle. rectIntersect.bottom = rectIntersect.top + nTmpHeight; // Copy the bitmap section from the current // layer's bitmap into the temporary layer's bitmap. CLayerInfo::CopyBitmapSection( pCurrentLayer->m_bmp, pTmpLayer->m_bmp, rectIntersect, &dc ); // Draw the current layer onto the bitmap. DrawLayerOnBitmap( &bmpTemp, pTmpLayer ); delete pTmpLayer; } } } } // Are we moving the current layer with the mouse? if ( true == pCurrentLayer->m_bTracking ) { // Draw the current layer onto the bitmap. DrawLayerOnBitmap( &bmpTemp, pCurrentLayer ); // Store a pointer to the tracked layer so that // we can determine if we need to draw each // of the visible layers above this one. pTrackedLayer = pCurrentLayer; // Only redraw the layers above // the tracked layer if requested to do so. if ( false == pCurrentLayer->m_bShowOnTopWhileTracking ) { // Now, set the flag so that all of the visible // layer above this one will be redrawn. bShowTopLayers = true; } } // Show the focus rectangle if necessary. if ( true == pCurrentLayer->m_bShowFocusRectangle ) { // Create a temporary layer to display // a rectangle around the layer being tracked. CLayerInfo *pLayerInfo = NULL; CSize sizeBitmap; sizeBitmap = pCurrentLayer->m_bmp.GetBitmapDimension(); // Create the focus layer based on the size of the current layer object. pLayerInfo = CreateFocusLayer( &dc, pCurrentLayer->m_ptLocation.x, pCurrentLayer->m_ptLocation.y, sizeBitmap.cx, sizeBitmap.cy, pCurrentLayer->m_colFocusRectangle ); if ( NULL != pLayerInfo ) { // Draw the temporary layer onto the bitmap. DrawLayerOnBitmap( &bmpTemp, pLayerInfo ); delete pLayerInfo; } } } // Select the combined bitmap into the display device context. pOldBmp = displayDC.SelectObject( &bmpTemp ); // Copy the combined bitmap onto the static control. dc.BitBlt( rectCtrl.left, rectCtrl.top, rectCtrl.Width(), rectCtrl.Height(), &displayDC, 0, 0, SRCCOPY ); // Cleanup the display device context. displayDC.SelectObject( pOldBmp ); // Do not call CStatic::OnPaint() for painting messages } Acknowledgements
History
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||