65.9K
CodeProject 正在变化。 阅读更多。
Home

CLayeredBitmapCtrl

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (44投票s)

2003年10月1日

CPOL

12分钟阅读

viewsIcon

197448

downloadIcon

11502

一个CStatic派生控件,允许在同一个控件中显示或隐藏多个位图层。

这是一个扩展CLayeredBitmapCtrl以在CScrollView应用程序中使用的示例。

引言

本文介绍了一个支持多位图层的CStatic派生类。CLayeredBitmapCtrl类允许您在静态控件上添加/删除/显示/隐藏位图层。每个位图层都可以具有用户定义的透明颜色,或者从位图的指定位置检索透明颜色。这样,背景位图就可以透过另一个位图层的透明颜色可见。还使用了工具提示,以便每个图层都可以显示其自己的描述。每个图层还可以有一个非透明区域,允许仅当鼠标悬停在该图层非透明部分时显示工具提示。第一个图层通常不设置为透明,因为它用作背景。所有其他图层都应设置透明标志,否则第一个图层将不可见,除非该图层较小且为矩形。

背景

我编写此代码是因为我需要一种方法来显示一个包含多个较小图像的背景图像。我还希望有一种快速添加和删除图像的方法。当鼠标悬停在某个图像上时,能够获取有关该图像的更多信息是必须的。这就是为什么我使用一种技术来创建一个非透明区域,以便只有图像的可见部分才会触发工具提示描述。

新功能(为2.01版添加)

  • 能够指定控件的背景颜色。以前,背景总是黑色的(默认)。
  • 控件的背景颜色也可以设置为使用系统颜色。
  • 作为额外奖励,控件将反映系统颜色的变化。

现有功能

为2.0版添加

  • 能够通过鼠标在控件内移动图层。
  • 当在图层中单击鼠标左按钮时使用焦点矩形。
  • 焦点矩形设置(包括颜色)是依赖于图层的。
  • 添加了用于更改各种图层属性的方法,包括描述、透明颜色和位置。
  • 添加了Create函数,以便控件可以在CView等中动态创建。

来自初始版本

  • CStatic派生,使其易于添加到对话框窗口。
  • 支持多位图层。
  • 易于添加、删除、显示和隐藏图层。
  • 能够交换图层。
  • 启用工具提示以帮助识别包含区域的每个可见图层。

CLayerInfo 类

CLayerInfo类包含图层对象的所有必要元素。比较函数仅用于在CLayeredBitmapCtrl控件中排序向量。CLayeredBitmapCtrl类包含一个CLayerInfo对象的向量。将CLayerInfo对象的副本添加到向量中,这意味着您可以在创建图层时使用临时的CLayerInfo变量。

构造/析构

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

    // Destructor
    virtual ~CLayerInfo();

实现

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

属性

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 类

CLayeredBitmapCtrl是一个CStatic派生类,它提供了在静态控件上添加/删除/显示/隐藏位图层的能力。第一个图层通常不设置为透明,因为它用作背景。所有其他图层都应设置透明标志,否则第一个图层将不可见。例外情况是,如果您有多个背景图层,并且只为其中一个设置了显示标志。

构造/析构

public:
    // Default CStatic contructor.
    CLayeredBitmapCtrl();

    // Destructor - Frees all of the memory associated with the layers.
    virtual ~CLayeredBitmapCtrl();

属性

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;

公共方法

  • void CLayeredBitmapCtrl::InitToolTips();

    初始化工具提示控件。

  • void CLayeredBitmapCtrl::ShowToolTips( bool bShow );

    启用或禁用工具提示。

  • bool CLayeredBitmapCtrl::ToolTipsEnabled() const;

    指示工具提示是否已启用。

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

    指定是否使用系统颜色作为控件的背景。

  • bool CLayeredBitmapCtrl::UsingSysColor() const;

    指示控件是否正在使用系统颜色。

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

    允许用户更改控件的背景颜色。

  • COLORREF CLayeredBitmapCtrl::GetCtrlBGColor();

    获取控件的当前背景颜色。

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

    将图层对象添加到向量中。

  • bool CLayeredBitmapCtrl::RemoveLayer( int nLayerID );

    根据图层ID从向量中删除图层对象。

  • bool CLayeredBitmapCtrl::ReindexLayers();

    允许根据图层对象在向量中的位置对其进行重新索引。如果删除了图层,则需要此操作。

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

    交换两个图层对象的索引。

  • bool CLayeredBitmapCtrl::MakeTopLayer( int nLayerID );

    将指定的图层移到所有其他图层的最上方。

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

    指示是否显示或隐藏此图层。

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

    设置图层透明标志和颜色(如果需要)。透明颜色默认为黑色。

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

    设置图层透明标志和图层对象位图内的透明像素的位置。

  • bool CLayeredBitmapCtrl::GetLayerTransparency( int nLayerID );

    获取指定图层对象的透明标志。

  • COLORREF CLayeredBitmapCtrl::GetLayerTransparencyColor( int nLayerID );

    获取指定图层对象的透明颜色。

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

    设置指定图层对象的描述。

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

    获取指定图层对象的描述。

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

    设置图层对象的跟踪启用标志。

  • bool CLayeredBitmapCtrl::GetLayerTracking( int nLayerID );

    获取图层对象的跟踪启用标志。

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

    设置图层对象的跟踪时置顶显示标志。

  • bool CLayeredBitmapCtrl::GetLayerShowOnTopWhileTracking( int nLayerID );

    获取图层对象的跟踪时置顶显示标志。

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

    设置图层对象的左上角位置点。

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

    获取图层对象的左上角位置点。

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

    获取图层对象的宽度和高度。

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

    设置图层对象的焦点矩形启用标志。

  • bool CLayeredBitmapCtrl::GetLayerEnableFocusRectangle( int nLayerID );

    获取图层对象的焦点矩形启用标志。

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

    设置图层对象的焦点矩形颜色。

  • COLORREF CLayeredBitmapCtrl::GetLayerFocusRectangleColor( int nLayerID );

    获取图层对象的焦点矩形颜色。

  • int CLayeredBitmapCtrl::GetLayerIndex( int nLayerID );

    获取图层对象的索引。

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

    根据控件的客户区矩形计算图层的中心偏移量。第二个实现只能在图层对象已存在于向量中时使用。

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

    创建由图层位图的非透明像素组成的区域。第二个实现只能在图层对象已存在于向量中时使用。

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

    确定两个图层的边界矩形是否相交。第二个实现只能在图层对象已存在于向量中时使用。

  • bool CLayeredBitmapCtrl::SortLayers();

    根据图层索引对图层对象进行排序。注意:没有检查以确保每个索引只有一个图层对象。

  • bool CLayeredBitmapCtrl::CombineLayers();

    将所有可见图层合并为一个位图,该位图将在OnPaint函数中显示。

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

    将指定的图层绘制到位图上。

  • bool CLayeredBitmapCtrl::ShowVisibleLayers();

    此函数确保在绘制到屏幕之前对图层进行排序和合并。

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

    在指定位置以指定的颜色创建一个临时的焦点矩形图层。

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

    允许动态创建控件。

使用代码

要将此控件用于您的应用程序,请将LayeredBitmapCtrl.hLayeredBitmapCtrl.cpp文件添加到您的项目中。使用资源编辑器将一个Picture Control添加到您的对话框中。编辑Picture Control的属性,并将控件名称从IDC_STATIC重命名为其他名称,例如IDC_LAYERED_DISPLAY。接下来,为控件添加Notify样式。这允许工具提示正常工作。

有两种方法可以为控件添加变量。

  1. 为了使CLayeredBitmapCtrlClassWizard中显示为选项,您必须首先删除.clw文件。删除文件后,打开ClassWizard。您将看到一个对话框,询问您是否要从源文件中重新创建ClassWizard数据库。选择“是”,并确保LayeredBitmapCtrl.hLayeredBitmapCtrl.cpp位于项目目录中。现在,选择您的对话框,然后选择Member Variables。选择IDC_LAYERED_DISPLAY控件(或您为其指定的名称)并添加一个变量。键入变量名,并将Category选择为Control。您应该在Variable type下看到CLayeredBitmapCtrl
  2. 打开ClassWizard,选择您的对话框,然后选择Member Variables。选择IDC_LAYERED_DISPLAY控件(或您为其指定的名称)并添加一个变量。键入变量名,并将Category选择为Control。选择CStatic作为Variable type。完成ClassWizard后,打开对话框的头文件。将变量的控件类型从CStatic更改为CLayeredBitmapCtrl

别忘了在您的对话框的头文件中添加以下内容

#include "LayeredBitmapCtrl.h"

确保每个图层都有唯一的图层ID。对于演示项目,我创建了一个枚举列表,以便我知道每个ID都是唯一的。我将图层设置在一个函数中,该函数在InitDialog函数末尾调用。但是,图层可以随时创建。此外,在InitDialog中,您可以指定控件的背景颜色。在演示中,我使用了CLayeredBitmapCtrl::UseSysColor函数,以便控件的背景颜色能够反映系统颜色的变化。

CLayeredBitmapCtrl类使用另一个名为CLayerInfo的类。这个类包含每个图层的所有信息和数据对象,包括CBitmapCRgn

如何设置图层

这是我在演示中用于设置各个图层的方法。对于第一个图层,我不指定透明颜色或创建区域。位图的位置默认为控件的左上角。由于该图层与控件大小相同,因此无需更改其位置。

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

这是一个透明位图的示例。我使用品红色RGB(255,0,255)作为透明颜色,因为它不太可能出现在我的背景图像中。

例如

变色龙鲍勃

如果您想显示一个图层,请在调用CLayeredBitmapCtrl::AddLayer函数之前添加以下代码

    pLayerInfo->m_bVisible    = true;

或者,您可以在添加图层后通过调用CLayeredBitmapCtrl::SetLayerVisibility函数来指定其可见性。

    m_layeredDisplay.SetLayerVisibility( CHAMELEON_BOB_LAYER, true );

设置好图层后,调用CLayeredBitmapCtrl::ShowVisibleLayers函数在控件上显示合并后的位图层。

    m_layeredDisplay.ShowVisibleLayers();

关注点

我查找了各种透明位图的示例,并阅读了Damon Chandler和Michael Fotsch所著的《Windows 2000 Graphics API Black Book》中的一个章节,最终找到了解决我问题的方法。由于位图的大小不同,需要进行大量调整才能正确实现。为了允许焦点矩形和移动图层的能力,我提出了一个想法,即能够将图层绘制到任何位图上,而不仅仅是合并后的位图。这是用于将可见图层合并到一个位图的代码,以及将图层绘制到任何位图上的代码。

//*******************************************************************
//  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;
}

这是OnPaint函数。在该函数中,会创建一个合并后位图的副本,以便在必要时绘制焦点矩形。此外,根据m_bShowOnTopWhileTracking标志,顶层图层将在跟踪图层上方重新绘制。如果设置了该标志,代码将确定围绕跟踪图层的矩形是否与当前图层相交。如果两个矩形相交,则只重绘当前图层与跟踪图层相交的部分。这仍然是一个缓慢的过程,但效果很好。这就是为什么我添加了这个标志,以便跟踪图层可以显示在所有其他图层之上,而无需重绘它们。请注意,跟踪图层的索引不会被修改。因此,一旦释放鼠标按钮,跟踪图层将再次绘制在可见的顶层图层之下。

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

致谢

  • Ivaylo Byalkov - 感谢他提供的PrepareRGBBitmapInfo函数,该函数来自他的Accelerated Smooth Bitmap Resizing文章,使得复制位图更加容易。
  • David Forrester - 我使用了他MakeRegion函数的修改版本,该函数来自他的Irregular Shaped Bitmap Dialog文章,用于为每个位图层(如果需要)创建一个非透明区域。
  • X.Q.Wang - 感谢他为选定图层周围的焦点矩形提出的建议。
  • Shilps - 感谢他发现了在VC++ 7.0 .NET下编译时FindLayer函数中的一个编译器错误。
  • Michel Wassink - 感谢他提出的允许用户更改控件背景颜色,并允许控件反映系统颜色变化的建议。

历史

  • 1.0 - 2003年9月27日
    • 初始发布。
  • 2.0 - 2004年6月17日
    • 增加了通过鼠标移动图层的能力。
    • 改进了绘图代码,以便可以将图层绘制到任何位图上。
    • 更改了演示应用程序以展示一些新功能。
    • 添加了焦点矩形以辅助图层选择。此方法可以在图层级别开启或关闭。
  • 2.01 - 2004年12月8日
    • 增加了更改控件背景颜色的能力,并在使用系统颜色时反映系统颜色变化。
    • 希望这次我修正了VC++ 7.0 .NET的所有编译器错误。
© . All rights reserved.