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

CExBitmap:一个具有撤销/重做功能的 CBitmap 扩展类

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.60/5 (9投票s)

2004年1月23日

7分钟阅读

viewsIcon

72558

downloadIcon

2957

CExBitmap 是一个 CBitmap 派生类, 设计用于位图绘画程序

引言

CExBitmap 类主要目的是提供对附加到 CExBitmap 对象上的位图进行撤销和重做的能力。为了完成这些任务,有必要提供在 CExBitmap 对象中存储当前附加位图备份副本的能力。通过允许类中存储多个备份,它变得更加灵活,使其对绘图程序更有用。

为了进一步增强 CExBitmap 类,我决定创建实用类 CDibDataCDibData 类的目的是支持位图图像的直接操作,并提供用于加载、保存和颜色深度转换位图的方法。CDibData 用于简化位图的 90 度增量旋转,以及用于加载和保存位图。

撤销和重做数组存储了我称之为部分位图的内容。部分位图是代表原始位图的某个部分(子位图)的位图,以减少存储位图所需的内存。部分位图对象存储部分位图、复制它的原始位图的大小以及表示它被复制的位置(相对于原始源位图左上角)的矩形。当发生撤销或重做时,源大小信息用于将位图恢复到保存时的大小,并且位置矩形用于将部分位图复制到其被复制的位置,反转该操作的信息会自动存储在相反的数组中,以便可以反转该操作。如果达到撤销/重做数组的限制,则删除第一个项目,并将存储在数组中的所有项目向下移动一位,新存储的项目放在数组的末尾(后进先出)。撤销数组从备份位图中复制部分位图数据,因此在对原始位图进行任何更改后,有必要更新备份。重做数组从原始位图中复制部分位图。撤销/重做数组的最大大小由用户设置,并且仅受可用内存量(或可以存储在对象数组中的最大项目数)的限制。

备份数组始终包含与原始位图大小相同的位图,以减少重新分配的次数。这是因为我预计只会存储很少的备份,通常不超过一两个。备份数组还将位图存储为部分位图。可以存储的备份数量仅受可用内存量(或可以存储在对象数组中的最大项目数)的限制。

CExBitmap 所需的文件

文件 目录
ExBitmap.h 扩展位图类头文件
ExBitmap.cpp 扩展位图类代码
CDibData.h CExBitmap 使用的 DIB 数据类
CDibData.cpp DIB 数据类代码
CWorkDC.h CExBitmap 使用的工作 DC 类
CWorkDC.cpp 工作 DC 类代码
Quantize.h CDibData 使用的 CQuantizer
Quantize.cpp CQuantizer 类代码
MyTrace.h CExBitmapCDibData 使用的调试跟踪函数
MyTrace.cpp 调试跟踪代码

使用代码

加载位图
您可以以任何您喜欢的方式加载位图,因为 CExBitmap 类只需要一个位图句柄即可完成其工作。我提供了 LoadDIB() 方法来简化位图的加载,并支持 LoadImage() 不支持的位图类型(即自顶向下位图)。

保存位图
调用 SaveDIB(),它支持以下功能:压缩(4 和 8 bpp)、颜色深度转换和颜色调色板优化。

使用撤销/重做功能
1. 如果您希望使用除默认值 10 以外的撤销/重做限制,请调用 SetUndoSize()
2. 在修改位图之前调用 SaveUndo()
3. 在修改位图之后调用 Backup()
4. 在您的撤销处理程序中调用 Undo()
5. 在您的重做处理程序中调用 Redo()

注意:如果您使用了 EnableAutoUndo() 来为 Flip()Rotate() 启用自动撤销/重做,那么对于这些方法您不需要执行上述操作。

绘图方法

方法 目的
绘制 简化位图的绘图。
TransBlt 透明位块传输,支持大多数 Windows 版本。
DrawBackup 如果定义了 TESTING_UNDOREDO
DrawUndo 如果定义了 TESTING_UNDOREDO
DrawRedo 如果定义了 TESTING_UNDOREDO

操作方法

方法 目的
CopyBitmap 支持完整、部分和拉伸复制。
ExpandBitmap 扩展/缩小位图尺寸。
ClearBimap 将位图的全部或部分清除为给定颜色。
Flip 水平、垂直或同时翻转位图。
FlipHorizontal 水平翻转位图。
FlipVertical 垂直翻转位图。
旋转 顺时针旋转位图 90、180 或 270 度。

注意:如果您的程序只会在 NT 3.1 及以上版本运行,那么您可以在项目中定义 WINNT31_ROTATE,以便通过世界变换来完成旋转。

备份方法

方法 目的
UseBackup 设置当前选定的备份图像。
RestoreBackup 从备份中恢复位图,可进行部分或完整恢复。
RemoveBackups 从内存中释放所有备份。
GetBackupSize 备份数组的当前大小(数组内容可能无效)。
IsBackupValid 确定当前选定的备份是否有效。

撤销/重做方法

方法 目的
SetUndoSize 设置撤销和重做数组的最大大小。
SaveUndo 将位图的全部或部分保存到撤销数组的末尾。
撤销 恢复撤销数组末尾存储的位图部分,并将更改存储到重做数组。
重做 恢复撤销数组末尾存储的位图部分,并将更改存储到撤销数组。
RemoveUndo 释放撤销数组中存储的所有撤销操作。
RemoveRedo 释放重做数组中存储的所有重做操作。
GetUndoSize 获取当前存储在撤销数组中的项目数。
GetRedoSize 获取当前存储在重做数组中的项目数。
IsModified 确定位图是否已被修改;基于需要多少撤销操作才能将位图恢复到其原始状态。
EnableAutoUndo 自动将项目放入撤销和重做数组,仅适用于 Rotate()Flip()

加载和保存

方法 目的
SaveDIB 将位图保存到文件,可用于颜色深度转换。
LoadDIB 从文件加载位图,包括自顶向下位图。

使用撤销/重做方法的示例

// General
void CExBmpDemoView::WhatEver() 
{
    CExBmpDemoDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    
    // save to undo array
    pDoc->m_exBitmap.SaveUndo();
    
    // call code to modify bitmap here
    
    // save backup copy of modified bitmap
    pDoc->m_exBitmap.Backup();
    
    // invalidate area of window where bitmap is displayed
}
// If EnableAutoUndo(TRUE).
void CExBmpDemoView::OnRotate180() 
{
    CExBmpDemoDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    if(pDoc->m_exBitmap.Rotate(1) )
    {
        AdjustScrollBars();    // height & width may have changed
        Invalidate();
    }
}
// If EnableAutoUndo(FALSE).
void CExBmpDemoView::OnRotate180() 
{
    CExBmpDemoDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    
    // saving entire bitmap    to undo array
    pDoc->m_exBitmap.SaveUndo();
    if( pDoc->m_exBitmap.Rotate(1) )
    {
        // saving entire bitmap to current
        backuppDoc->m_exBitmap.Backup();
        
        AdjustScrollBars();
        Invalidate();
    }
}
/**
Flips or Rotates currently selected portion of image.
If the height & width of portion to be rotated are not the same,
or not rotate 180 degrees, then the selection flag is set to
FALSE, since the selection rectangle is no longer valid.
*/
void CExBmpDemoView::FlipOrRotate(BOOL bFlip, int nDirection)
{
    // Note: Auto Undo is was activated in ExBmpDemoDoc.cpp.
    // Therefore, we do not need to manualy call SaveUndo() and Backup()
    // when rotating or flipping main bitmap stored in document.
    CExBmpDemoDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    if( pDoc->m_exBitmap.GetSafeHandle() )
    {
        if( m_bRectSelected )
        {
            if( m_nMag < 1 )
                m_nMag = 1;

            // get converted copy of selection rect.
            m_rectSel.OffsetRect(-m_nBorderWidth, -m_nBorderWidth);
            CRect rectSrc(m_rectSel.left/m_nMag, m_rectSel.top/m_nMag,
                m_rectSel.right/m_nMag, m_rectSel.bottom/m_nMag);
            m_rectSel.OffsetRect(m_nBorderWidth, m_nBorderWidth);

            // create bitmap compatible with main bitmap
            CExBitmap exTempBitmap;
            {
            CWorkDC dcWork(NULL,&pDoc->m_exBitmap);
            if( !exTempBitmap.CreateCompatibleBitmap(
               &dcWork, rectSrc.Width(), rectSrc.Height()) )
                return;
            }

            // get copy of selected area
            CRect rectDest(0, 0, rectSrc.Width(), rectSrc.Height());
            if( !exTempBitmap.CopyBitmap(
                   pDoc->m_exBitmap, rectSrc, rectDest) )
                return;

            if( bFlip )
            {
                // flip copy
                if( !exTempBitmap.Flip(nDirection) )
                    return;
            }
            else
            {
                // rotate copy
                if( !exTempBitmap.Rotate(nDirection) )
                    return;
            }

            // Save modified bitmap area to undo array
            SIZE sizeTempBitmap = exTempBitmap.GetSize();
            CRect rectUndo(rectSrc.left, rectSrc.top,
                rectSrc.left + max(sizeTempBitmap.cx, rectSrc.Width()),
                rectSrc.top  + max(sizeTempBitmap.cy, rectSrc.Height()));
            pDoc->m_exBitmap.SaveUndo(&rectUndo);

            // clear selected rect.
            pDoc->m_exBitmap.ClearBitmap(RGB(255,255,255), &rectSrc);

            // Copy fliped/rotated bitmap back to main bitmap
            pDoc->m_exBitmap.CopyBitmap(
               exTempBitmap, FALSE, rectSrc.left, rectSrc.top);

            // Save backup (needed for future SaveUndo() calls)
            pDoc->m_exBitmap.Backup();

            if( !bFlip )
            {
                // reset selection state ?
                if( nDirection != 1 ) // != 180 degrees
                {
                    // If selection rect. is invalid
                    if( sizeTempBitmap.cx != rectSrc.Width() ||
                        sizeTempBitmap.cy != rectSrc.Height() )
                    {
                        m_bRectSelected = FALSE;
                    }
                }
            }
            // else no need to reset selection rect. 
            // (effected area has not changed)

            AdjustScrollBars();
            Invalidate();
        }
        else if( bFlip )
        {
            if( pDoc->m_exBitmap.Flip(nDirection) )
            {
                AdjustScrollBars();    // need to adjust for 
                          // showing of Undo & Redo bitmaps
                Invalidate();
            }
        }
        else
        {
            if( pDoc->m_exBitmap.Rotate(nDirection) )
            {
                AdjustScrollBars();    // height & width may have changed
                Invalidate();
            }
        }
    }
}

关注点

您可以使用 Doxygen.dat(在 ExBitmap 目录中)文件和 Doxywizard 来为该类生成 HTML 文档。您可以在 Doxygen.org 下载 Doxygen 和 Doxywizard。

对于关心的人来说:我已经更改了 ExBitmap.xxxCDibData.xxx 中使用的注释风格,以改进 Doxygen 生成的文档。

一些问答

  • 问题:为什么可能需要能够保存多个位图备份?
  • 回答:当我为自己设计一个位图编辑器时:我决定在绘制多边形时,我希望能够一步撤销绘图,而不是一次撤销一条线。解决方案是使用两个备份:一个用于正常的撤销/重做,另一个用于在绘制多边形时使用。
  • 问题:为什么在“撤销”和“重做”标题下显示的用于部分位图的周围会绘制一个彩色矩形?
  • 回答:彩色矩形用于显示(显示的)部分位图相对于其复制的原始位图的位置关系。
  • 问题:为什么使用单独的实用类(CDibData)来直接访问位图数据?
  • 回答:我先创建了 CExBitmap,并且不喜欢弄乱它。
  • 问题:除了撤销/重做功能之外,您认为代码的哪个部分值得研究?
  • 回答:看看 Flip()Rotate() 方法。我提供了两种旋转方法:一种使用 CDibData 对象,另一种使用世界变换(仅限 NT/2000 及以上版本)。
  • 问题CDibData 类本身有用吗?
  • 回答:请参阅文章 CDibData
  • 问题:参考文献在哪里?
  • 回答:创建此类时我使用的唯一参考是 MSDN。现在,CDibData 则需要大量的研究(参考列表在 CDibData.cpp 中)。
© . All rights reserved.