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






3.60/5 (9投票s)
2004年1月23日
7分钟阅读

72558

2957
CExBitmap 是一个 CBitmap 派生类,
引言
CExBitmap
类主要目的是提供对附加到 CExBitmap
对象上的位图进行撤销和重做的能力。为了完成这些任务,有必要提供在 CExBitmap
对象中存储当前附加位图备份副本的能力。通过允许类中存储多个备份,它变得更加灵活,使其对绘图程序更有用。
为了进一步增强 CExBitmap
类,我决定创建实用类 CDibData
。CDibData
类的目的是支持位图图像的直接操作,并提供用于加载、保存和颜色深度转换位图的方法。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 | CExBitmap 和 CDibData 使用的调试跟踪函数 | |
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.xxx 和 CDibData.xxx 中使用的注释风格,以改进 Doxygen 生成的文档。
一些问答
- 问题:为什么可能需要能够保存多个位图备份?
- 回答:当我为自己设计一个位图编辑器时:我决定在绘制多边形时,我希望能够一步撤销绘图,而不是一次撤销一条线。解决方案是使用两个备份:一个用于正常的撤销/重做,另一个用于在绘制多边形时使用。
- 问题:为什么在“撤销”和“重做”标题下显示的用于部分位图的周围会绘制一个彩色矩形?
- 回答:彩色矩形用于显示(显示的)部分位图相对于其复制的原始位图的位置关系。
- 问题:为什么使用单独的实用类(
CDibData
)来直接访问位图数据? - 回答:我先创建了
CExBitmap
,并且不喜欢弄乱它。
- 问题:除了撤销/重做功能之外,您认为代码的哪个部分值得研究?
- 回答:看看
Flip()
和Rotate()
方法。我提供了两种旋转方法:一种使用CDibData
对象,另一种使用世界变换(仅限 NT/2000 及以上版本)。
- 问题:
CDibData
类本身有用吗? - 回答:请参阅文章 CDibData。
- 问题:参考文献在哪里?
- 回答:创建此类时我使用的唯一参考是 MSDN。现在,CDibData 则需要大量的研究(参考列表在 CDibData.cpp 中)。