Click here to Skip to main content
15,881,248 members
Articles / Desktop Programming / MFC
Article

CExBitmap : a CBitmap extension class with undo/redo

Rate me:
Please Sign up or sign in to vote.
3.60/5 (9 votes)
22 Jan 20047 min read 71.2K   2.9K   35   9
CExBitmap is a CBitmap derived class designed for use in bitmap paint programs

Image 1

Introduction

The main purpose of the CExBitmap class is to provide the capability to undo and redo changes to the bitmap attach to the CExBitamp object. To accomplish these tasks it was necessary to provide the ability to store a backup copy of the currently attach bitmap in the CExBitmap object as well. By allowing multiple backups to be stored in the class it becomes much more flexible, which makes it more useful for paint programs.

To further enhance the CExBitmap class I decided to create the utility class CDibData. The purpose of the CDibData class is to provide support for the direct manipulation of the bitmaps image, as well as provide methods for: loading, saving, and color-depth conversion of bitmaps. CDibData is used to simplify the rotation of bitmaps in 90 degree increments, as well as for loading and saving of bitmap.

The undo and redo arrays store what I call partial bitmaps. A partial bitmap is a bitmap that represents only a part (sub-bitmap) of the original bitmap, in order to reduce the a memory required to store the bitmaps. A partial bitmap object stores the partial bitmap, the size of the original bitmap from which it was copied, and the rectangle representing the location (relative to the upper left hand corner of original source bitmap) from which it was copied. When an undo or redo occurs, the source size information is used to restore the bitmap to the size it was at the time of saving and the positional rectangle is used to copy the partial bitmap to the location from which is was copied, the information to reverse the action is automatically stored in the opposite array, so that the action can be reversed. If the undo or redo array limit is reached then the first item is deleted and all items stored in the array are moved down by one and the new item being stored is placed at the end of the array (LIFO). The undo array copies the partial bitmap data from the backup bitmap, therefore it is necessary to update the backup after making any changes to the original bitmap. The redo array copies the partial bitmap from the original bitmap. The maximum size of the undo/redo array is settable by the user and is only limit by the amount of memory available (or maximum number of items that can be store in an object array).

The backup array always contains bitmaps the same size as the original, in order to reduce the number of reallocation needed. This is because I expect very few backups to be stored, usually no more than one or two. The backup array also stores the bitmaps as partial bitmaps. The number of backups that can be stored is only limit by the amount of memory available (or maximum number of items that can be store in an object array).

Files required by CExBitmap

FileContents
ExBitmap.hExtended bitmap class header
ExBitmap.cppExtended bitmap class code
CDibData.hDIB data class used by CExBitmap
CDibData.cppDIB data class code
CWorkDC.hWorking DC class used by CExBitmap
CWorkDC.cppWorking DC class code
Quantize.hCQuantizer class used by CDibData
Quantize.cppCQuantizer class code
MyTrace.hDebug trace functions used by CExBitmap and CDibData
MyTrace.cppDebug trace code

Using the code

Loading a bitmap:
You may load the bitmap in any way you wish, as the CExBitmap class only needs a bitmap handle in order to do its' thing. I have provided the method LoadDIB(), to simplify loading of bitmap and to support bitmap types that are not supported by LoadImage() (namely top-down bitmaps).

Saving a bitmap:
Call SaveDIB() which supports the following features: compression (4 and 8 bpp), color-depth conversion, and color palette optimization.

Using the undo/redo features:
1. Call SetUndoSize() if you wish to use an undo/redo limit other than the default limit of 10.
2. Call SaveUndo() before modifying bitmap.
3. Call Backup() after modifying bitmap.
4. Call Undo() from inside your undo handler.
5. Call Redo() from inside your redo handler.

Note: If you used EnableAutoUndo() to enable automatic undo/redo for Flip() and Rotate(), then you do not need to do the above for those methods.

Drawing methods

MethodPrupose
DrawSimplifies drawing of bitmap.
TransBltTransparent blit, supports most versions of windows.
DrawBackupIf TESTING_UNDOREDO defined.
DrawUndoIf TESTING_UNDOREDO defined.
DrawRedoIf TESTING_UNDOREDO defined.

Manipulation methods

MethodPrupose
CopyBitmapSupports full, partial, and stretch copying.
ExpandBitmapExpands/Shrinks bitmap size.
ClearBimapClears all or part of the bitmap to a given color.
FlipFlips bitmap either horizontally, vertically, or both.
FlipHorizontalFlips bitmap horizontally.
FlipVerticalFlips bitmap vertically.
RotateRotates bitmap by 90, 180, or 270 degrees clockwise.

Note: If your program is only going to run on NT 3.1 and above the you can define WINNT31_ROTATE in you project so that rotation is accomplished using world transforms.

Backup methods

MethodPrupose
UseBackupSets which backup image is currently selected.
RestoreBackupRestores bitmap from backup, partial or full restore.
RemoveBackupsFrees all backups from memory.
GetBackupSizeCurrent size of backup array (contents of array may not be valid).
IsBackupValidDetermines if the currently selected backup is valid.

Undo/Redo methods

MethodPrupose
SetUndoSizeSets the maximum size of the undo and redo arrays.
SaveUndoSaves all or part of the bitmap to end of undo array.
UndoRestores the part of the bitmap stored at the end of undo array and stores the change to the redo array.
RedoRestores the part of the bitmap stored at the end of the undo array and stores the change to the undo array.
RemoveUndoFrees all undoes stored in the undo array.
RemoveRedoFrees all redoes stored in the redo array.
GetUndoSizeGet the number of items currently stored in the undo array.
GetRedoSizeGet the number of items currently stored in the redo array.
IsModifiedDetermines if the bitmap has been modified; based on the number of undoes it would require to restore bitmap back to its' original state.
EnableAutoUndoAutomatically place items in the undo and redo arrays, only applies to Rotate() and Flip().

Loading and Saving

MethodPrupose
SaveDIBSaves bitmap to file, can be used for color-depth conversion.
LoadDIBLoads bitmap from file, including top-down bitmaps.

Examples of using the undo/redo methods:

// 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();
            }
        }
    }
}

Points of Interest

You can use the Doxygen.dat (in the ExBitmap directory) file with the Doxywizard to generate HTML documentation for this class. You can download Doxygen and Doxywizard at Doxygen.org.

For those who care: I have change the comment style used in both ExBitmap.xxx and CDibData.xxx, to improve the documentation generated by Doxygen.

Some questions and answers:

  • Question: Why might you need to be able to save more than one backup of bitmap?
  • Answer: When I was designing a bitmap editor for my use: I decided that when drawing a polygon, that I wanted the ability to undo the drawing in one step instead of undoing it one line at a time. The solution was to use two backups: one for normal undoes/redoes and one for use while drawing the polygon.
  • Question: Why is there a colored rectangle drawn around the partial bitmaps displayed under the headings Undo and Redo?
  • Answer: The colored rectangle is used to show the positional relationship of the partial bitmap (displayed) to the origanal bitmap, from which it was copied.
  • Question: Why use a separate utility class (CDibData) to gain direct access to bitmap bits?
  • Answer: I created CExBitmap first and did not like messing it up.
  • Question: Other than the undo/redo features, what part of the code do you think is worth examining?
  • Answer: Take a look at the Flip() and Rotate() methods. I have provided two version of the rotation method: one that uses CDibData objects and one that uses world transforms (NT/2000 and above only).
  • Question: Is the CDibData class useful by itself?
  • Answer:See article CDibData.
  • Question: Where are the references?
  • Answer: The only reference I used when creating this class was MSDN. Now CDibData, on the other hand, required a lot of research (reference list in CDibData.cpp).

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


Written By
Software Developer (Senior)
United States United States
I am a senior software engineer who has been designing and developing software for many years, mostly in C/C++. You might say that I think in code; which is why I am passionate about my first rule of coding: “First do no harm”. So if I get carried away in my explanations, please realize that it is just part of my personality. I enjoy learning new things and, when I have the time, passing that knowledge onto others.

Comments and Discussions

 
QuestionSlow response using Magnify!!! Pin
Kiran Satish18-Nov-08 13:31
Kiran Satish18-Nov-08 13:31 
AnswerRe: Slow response using Magnify!!! Pin
John R. Shaw26-Nov-08 9:25
John R. Shaw26-Nov-08 9:25 
GeneralRe: Slow response using Magnify!!! Pin
Kiran Satish26-Nov-08 9:34
Kiran Satish26-Nov-08 9:34 
GeneralRe: Slow response using Magnify!!! Pin
John R. Shaw26-Nov-08 11:45
John R. Shaw26-Nov-08 11:45 
GeneralRe: Slow response using Magnify!!! Pin
Kiran Satish2-Dec-08 10:32
Kiran Satish2-Dec-08 10:32 
GeneralNo CExBitmap class Pin
cybernaut_ev12-Feb-08 13:30
cybernaut_ev12-Feb-08 13:30 
GeneralRe: No CExBitmap class Pin
John R. Shaw15-Feb-08 16:10
John R. Shaw15-Feb-08 16:10 
I did a quick download and looked - it is still there in the CExBitmap directory. The header file is ExBitmap.h and the code file is ExBitmap.cpp.

Take a look in the directories or try re-downloading.

Good Luck!

INTP
"Program testing can be used to show the presence of bugs, but never to show their absence."Edsger Dijkstra

GeneralJPEG format Pin
Bui Tan Duoc25-Jul-04 16:51
professionalBui Tan Duoc25-Jul-04 16:51 
GeneralRe: JPEG format Pin
John R. Shaw8-Aug-04 18:57
John R. Shaw8-Aug-04 18:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.