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

CExBitmap : a CBitmap extension class with undo/redo

0.00/5 (No votes)
22 Jan 2004 1  
CExBitmap is a CBitmap derived class designed for use in bitmap paint programs

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

File Contents
ExBitmap.h Extended bitmap class header
ExBitmap.cpp Extended bitmap class code
CDibData.h DIB data class used by CExBitmap
CDibData.cpp DIB data class code
CWorkDC.h Working DC class used by CExBitmap
CWorkDC.cpp Working DC class code
Quantize.h CQuantizer class used by CDibData
Quantize.cpp CQuantizer class code
MyTrace.h Debug trace functions used by CExBitmap and CDibData
MyTrace.cpp Debug 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

Method Prupose
Draw Simplifies drawing of bitmap.
TransBlt Transparent blit, supports most versions of windows.
DrawBackup If TESTING_UNDOREDO defined.
DrawUndo If TESTING_UNDOREDO defined.
DrawRedo If TESTING_UNDOREDO defined.

Manipulation methods

Method Prupose
CopyBitmap Supports full, partial, and stretch copying.
ExpandBitmap Expands/Shrinks bitmap size.
ClearBimap Clears all or part of the bitmap to a given color.
Flip Flips bitmap either horizontally, vertically, or both.
FlipHorizontal Flips bitmap horizontally.
FlipVertical Flips bitmap vertically.
Rotate Rotates 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

Method Prupose
UseBackup Sets which backup image is currently selected.
RestoreBackup Restores bitmap from backup, partial or full restore.
RemoveBackups Frees all backups from memory.
GetBackupSize Current size of backup array (contents of array may not be valid).
IsBackupValid Determines if the currently selected backup is valid.

Undo/Redo methods

Method Prupose
SetUndoSize Sets the maximum size of the undo and redo arrays.
SaveUndo Saves all or part of the bitmap to end of undo array.
Undo Restores the part of the bitmap stored at the end of undo array and stores the change to the redo array.
Redo Restores the part of the bitmap stored at the end of the undo array and stores the change to the undo array.
RemoveUndo Frees all undoes stored in the undo array.
RemoveRedo Frees all redoes stored in the redo array.
GetUndoSize Get the number of items currently stored in the undo array.
GetRedoSize Get the number of items currently stored in the redo array.
IsModified Determines if the bitmap has been modified; based on the number of undoes it would require to restore bitmap back to its' original state.
EnableAutoUndo Automatically place items in the undo and redo arrays, only applies to Rotate() and Flip().

Loading and Saving

Method Prupose
SaveDIB Saves bitmap to file, can be used for color-depth conversion.
LoadDIB Loads 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