Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

CLayeredBitmapCtrl

0.00/5 (No votes)
27 Jan 2005 1  
A CStatic derived control that allows multiple layers of bitmaps to be displayed or hidden in the same control.

Here is an example of how to extend the CLayeredBitmapCtrl to be used within a CScrollView application:

Introduction

This article is about a CStatic derived class that supports multiple bitmap layers. The CLayeredBitmapCtrl class allows you to add/remove/show/hide bitmap layers on a static control. Each bitmap layer can have a user defined transparent color or retrieve the transparent color from a specified location in the bitmap. This allows the background bitmap to be visible through the transparent color of another bitmap layer. Tool tips are also used so that each layer can display its own description. Each layer can also have a non-transparent region that allows the tool tip to be displayed only when the mouse is over the non-transparent portion of the layer. The first layer is not usually set to transparent because it is used as the background. All of the other layers should have the transparent flag set, otherwise the first layer will not be visible, unless the layer is smaller and rectangular.

Background

I 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)

  • Ability to specify the background color of the control. Previously, the background was always black (default).
  • The background color of the control can also be set to use the system color.
  • As an added bonus, the control will reflect changes to the system color.

Existing Features

Added for Version 2.0

  • Ability to move a layer with the mouse, within the control.
  • Use of a focus rectangle when the left mouse button is clicked in a layer.
  • Focus rectangle settings (including color) are layer dependent.
  • Added methods for changing various layer attributes, including description, transparent color and location.
  • Added a Create function so that the control can be created dynamically within a CView, etc.

From Initial Release

  • Derived from CStatic which makes it easy to add to a dialog window.
  • Support for multiple bitmap layers.
  • Easy to add, remove, show and hide layers.
  • Ability to swap layers.
  • Enable tool tips to help identify each visible layer that contains a region.

CLayerInfo Class

The CLayerInfo class contains all of the necessary elements for a layer object. The comparison functions are only provided for sorting the vector in the CLayeredBitmapCtrl control. The CLayeredBitmapCtrl class contains a vector of CLayerInfo objects. A new copy of the CLayerInfo object is added to the vector, which means that you can use a temporary CLayerInfo variable when creating layers.

Construction/Destruction

public:
    CLayerInfo();
    CLayerInfo( const CLayerInfo &src );  // Copy constructor


    // Destructor

    virtual ~CLayerInfo();

Implementation

public:
    // 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 );

Attributes

public:
    // 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 Class

The CLayeredBitmapCtrl is a CStatic derived class that provides the ability to add/remove/show/hide bitmap layers on a static control. The first layer is not usually set to transparent because it is used as the background. All of the other layers should have the transparent flag set, otherwise the first layer will not be visible. The exception to this is if you have more than one background layer and you only set the show flag for one or the other.

Construction/Destruction

public:
    // Default CStatic contructor.

    CLayeredBitmapCtrl();

    // Destructor - Frees all of the memory associated with the layers.

    virtual ~CLayeredBitmapCtrl();

Attributes

public:
    // 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

  • void CLayeredBitmapCtrl::InitToolTips();

    Initializes the tool tip control.

  • void CLayeredBitmapCtrl::ShowToolTips( bool bShow );

    Enables or disables tool tips.

  • bool CLayeredBitmapCtrl::ToolTipsEnabled() const;

    Indicates whether or not tool tips are enabled.

  • void CLayeredBitmapCtrl::UseSysColor( bool bUseSysColor, bool bRedraw = false );

    Specifies whether or not to use the system color for the background of the control.

  • bool CLayeredBitmapCtrl::UsingSysColor() const;

    Indicates if the control is using the system color.

  • void CLayeredBitmapCtrl::SetCtrlBGColor( COLORREF colCtrlBG, bool bRedraw = false );

    Allows the user to change the background color of the control.

  • COLORREF CLayeredBitmapCtrl::GetCtrlBGColor();

    Gets the current background color of the control.

  • bool CLayeredBitmapCtrl::AddLayer( CLayerInfo &layerInfo );

    Adds a layer object to the vector.

  • bool CLayeredBitmapCtrl::RemoveLayer( int nLayerID );

    Removes a layer object from the vector, based on the layer ID.

  • bool CLayeredBitmapCtrl::ReindexLayers();

    Allows the layer objects to be re-indexed based on their position in the vector. This is necessary if layers have been removed.

  • bool CLayeredBitmapCtrl::SwapLayers( int nFirstLayerID, int nSecondLayerID );

    Swaps the indices of two layer objects.

  • bool CLayeredBitmapCtrl::MakeTopLayer( int nLayerID );

    Moves the specified layer to the top of all other layers.

  • bool CLayeredBitmapCtrl::SetLayerVisibility( int nLayerID, bool bVisible );

    Indicates if this layer is to be displayed or hidden.

  • bool CLayeredBitmapCtrl::SetLayerTransparency( int nLayerID, bool bTransparent = false, COLORREF colTransparent = RGB(0, 0, 0) );

    Sets the layer transparency flag and color, if necessary. The transparent color is defaulted to black.

  • bool CLayeredBitmapCtrl::SetLayerTransparency( int nLayerID, bool bTransparent, CPoint &ptTransparentPixel );

    Sets the layer transparency flag and location of the transparent pixel within the layer object's bitmap.

  • bool CLayeredBitmapCtrl::GetLayerTransparency( int nLayerID );

    Gets the transparent flag of the specified layer object.

  • COLORREF CLayeredBitmapCtrl::GetLayerTransparencyColor( int nLayerID );

    Gets the transparent color of the specified layer object.

  • bool CLayeredBitmapCtrl::SetLayerDescription( int nLayerID, CString &strLayerDesc );

    Sets the description for the specified layer object.

  • bool CLayeredBitmapCtrl::GetLayerDescription( int nLayerID, CString &strLayerDesc );

    Gets the description of the specified layer object.

  • bool CLayeredBitmapCtrl::SetLayerTracking( int nLayerID, bool bTrackingEnabled );

    Sets the tracking enabled flag of a layer object.

  • bool CLayeredBitmapCtrl::GetLayerTracking( int nLayerID );

    Gets the tracking enabled flag of a layer object.

  • bool CLayeredBitmapCtrl::SetLayerShowOnTopWhileTracking( int nLayerID, bool bShowOnTopWhileTracking );

    Sets the show on top while tracking flag of a layer object.

  • bool CLayeredBitmapCtrl::GetLayerShowOnTopWhileTracking( int nLayerID );

    Gets the show on top while tracking flag of a layer object.

  • bool CLayeredBitmapCtrl::SetLayerLocation( int nLayerID, const CPoint &ptNewLocation );

    Sets the upper-left location point of a layer object.

  • bool CLayeredBitmapCtrl::GetLayerLocation( int nLayerID, CPoint &ptLocation );

    Gets the upper-left location point of a layer object.

  • bool CLayeredBitmapCtrl::GetLayerSize( int nLayerID, CSize &size );

    Gets the width and height of a layer object.

  • bool CLayeredBitmapCtrl::SetLayerEnableFocusRectangle( int nLayerID, bool bEnabled );

    Sets the focus rectangle enabled flag of a layer object.

  • bool CLayeredBitmapCtrl::GetLayerEnableFocusRectangle( int nLayerID );

    Gets the focus rectangle enabled flag of a layer object.

  • bool CLayeredBitmapCtrl::SetLayerFocusRectangleColor( int nLayerID, COLORREF colFocusRectangle );

    Sets the focus rectangle color of a layer object.

  • COLORREF CLayeredBitmapCtrl::GetLayerFocusRectangleColor( int nLayerID );

    Gets the focus rectangle color of a layer object.

  • int CLayeredBitmapCtrl::GetLayerIndex( int nLayerID );

    Gets the layer object's index.

  • bool CLayeredBitmapCtrl::CalculateCenterOffset( CLayerInfo *pLayerInfo );
  • bool CLayeredBitmapCtrl::CalculateCenterOffset( int nLayerID );

    Calculates the center offset for a layer based on the control's client rect. The second implementation can only be used if the layer object already exists in the vector.

  • bool CLayeredBitmapCtrl::CreateNonTransparentRgn( CLayerInfo *pLayerInfo );
  • bool CLayeredBitmapCtrl::CreateNonTransparentRgn( int nLayerID );

    Creates a region composed of the non-transparent pixels of the layer's bitmap. The second implementation can only be used if the layer object already exists in the vector.

  • bool CLayeredBitmapCtrl::DoLayersIntersect( CLayerInfo *pLayerInfo1, CLayerInfo *pLayerInfo2, CRect *pRectIntersect = NULL );
  • bool CLayeredBitmapCtrl::DoLayersIntersect( int nLayerID1, int nLayerID2, CRect *pRectIntersect = NULL );

    Determines if the bounding rectangle for two layers intersect. The second implementation can only be used if the layer object already exists in the vector.

  • bool CLayeredBitmapCtrl::SortLayers();

    Sorts the layer objects based on the layer index. Note: there are no checks to make sure that there is only one layer object per index.

  • bool CLayeredBitmapCtrl::CombineLayers();

    Combines all of the visible layers into one bitmap that will be displayed within the OnPaint function.

  • bool CLayeredBitmapCtrl::DrawLayerOnBitmap( CBitmap *pBmpBackground, CLayerInfo *pLayerInfo );

    Draws the specified layer onto a bitmap.

  • bool CLayeredBitmapCtrl::ShowVisibleLayers();

    This function makes sure that the layers are sorted and combined before drawing them onto the screen.

  • CLayerInfo *CLayeredBitmapCtrl::CreateFocusLayer( CDC *pDC, int nX, int nY, int nWidth, int nHeight, COLORREF colFocus );

    Creates a temporary focus rectangle layer at the specified location with the specified color.

  • BOOL CLayeredBitmapCtrl::Create( DWORD dwExStyle, const RECT &rect, CWnd *pParentWnd, UINT nID );

    Allows the control to be created dynamically.

Using the code

To 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 IDC_STATIC to some other name like IDC_LAYERED_DISPLAY. Next, add the Notify style to the control. This allows the tool tip to work properly.

There are two ways to add a variable for the control.

  1. In order to see the CLayeredBitmapCtrl listed as an option in the ClassWizard, you must first delete the .clw file. Once you have deleted the file, open the ClassWizard. You will see a dialog box asking if you want to recreate the ClassWizard database from your source files. Choose Yes and make sure that the LayeredBitmapCtrl.h and LayeredBitmapCtrl.cpp are in the project directory. Now, choose your dialog and select Member Variables. Select the IDC_LAYERED_DISPLAY control (or the name that you gave it) and add a variable. Type the variable name and select Control as the Category. You should see the CLayeredBitmapCtrl listed under Variable type.
  2. Open the ClassWizard, choose your dialog, and select Member Variables. Select the IDC_LAYERED_DISPLAY control (or the name that you gave it) and add a variable. Type the variable name and select Control as the Category. Choose CStatic for the Variable type. Once you are finished with the ClassWizard, open your dialog's header file. Change the control type from CStatic to CLayeredBitmapCtrl for your variable.

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 InitDialog function. However, the layers can be created at anytime. Also, within InitDialog, you can specify the control's background color. In the demo, I use the CLayeredBitmapCtrl::UseSysColor function so that the control's background color will reflect changes to the system color.

The CLayeredBitmapCtrl class uses another class called CLayerInfo. This class contains all of the information and data objects for each layer, including a CBitmap and CRgn.

How to setup layers

This 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 RGB(255,0,255) for the transparent color, because it's not very likely that I'll have that color in my background image.

For example

Chameleon Bob

If you want to have a layer displayed, add the following code before you call the CLayeredBitmapCtrl::AddLayer function:

    pLayerInfo->m_bVisible    = true;

Or, you can specify the visibility after the layer has been added, by calling the CLayeredBitmapCtrl::SetLayerVisibility function.

    m_layeredDisplay.SetLayerVisibility( CHAMELEON_BOB_LAYER, true );

Once you have setup the layers, call the CLayeredBitmapCtrl::ShowVisibleLayers function to display the combined bitmap layers on the control.

    m_layeredDisplay.ShowVisibleLayers();

Points of Interest

I 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 OnPaint function. In this function, a copy of the combined bitmap is made so that we can draw the focus rectangle if necessary. Also, depending on the m_bShowOnTopWhileTracking flag, the top layers will be redrawn above the tracked layer. If the flag is set, then the code determines if the rectangle surrounding the tracked layer intersects with the current layer. If the two rectangles intersect, then only the portion of the current layer that intersects with the tracked layer is redrawn. It is still a slow process, but a nice effect. That is why I added the flag, so that the tracked layer can be displayed on top of all of the other layers without having to redraw them. Note, that the tracked layer's index is not modified. So once the mouse button is released, the tracked layer will be drawn under the visible top layers again.

//*******************************************************************

//  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

  • Ivaylo Byalkov - For his PrepareRGBBitmapInfo function from his Accelerated Smooth Bitmap Resizing article, which made it easier for copying bitmaps.
  • David Forrester - I used a modified version of his MakeRegion function, from his Irregular Shaped Bitmap Dialog article, to make a non-transparent region for each of the bitmap layers (if desired).
  • X.Q.Wang - For his suggestion for a focus rectangle around the selected layer.
  • Shilps - For finding a compiler error in the FindLayer function when compiled with VC++ 7.0 .NET.
  • Michel Wassink - For his suggestion to allow the user to change the background color of the control and also allow the control to reflect system color changes.

History

  • 1.0 - 27 Sep 2003
    • Initial release.
  • 2.0 - 17 Jun 2004
    • Added the ability to move layers with the mouse.
    • Improved drawing code so that layers can be drawn on to any bitmap.
    • Changed the demo application to show some of the new features.
    • Added a focus rectangle to aid in the selection of a layer. This method can be turned on or off at the layer level.
  • 2.01 - 8 Dec 2004
    • Added the ability to change the control's background color, and reflect system color changes if the system color is used.
    • Hopefully, this time I corrected all of the compiler errors with VC++ 7.0 .NET.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here