Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / MFC
Article

Saving a Drawing to a Bitmap File

Rate me:
Please Sign up or sign in to vote.
4.33/5 (24 votes)
18 Jan 20053 min read 179.5K   8.5K   75   16
A simple way to save a drawing to a bitmap file.

Sample Image

Introduction

We sometimes encounter situations in which we need to save a drawing to a bitmap or other image file formats. For example, if your program edits images by writing text over them or by allowing the user to draw shapes or objects over the images, you might find it convenient to manipulate the images and objects as a single drawing and then save the complete drawing to file.

This article describes a simple way to save a drawing to a Windows bitmap file. Since the main point of this article is to show how to convert a drawing into an image file, the drawing part of the demo program is quite simple. It allows you to scribble free-hand shapes on a static control within a dialog box. When you are done with the drawing, you click the "Save" button, specify a path name, and then the program saves your entire scribbling to disk as a 24-bit Windows bitmap file.

A professional application skinning platform we have developed and that is freely downloadable from here uses some variation of the technique presented in this article to create and edit sophisticated icon images with full alpha-channel transparency support for use in professional application skins.

Using the code

The demo program is a dialog-based MFC application. All the interesting action takes place in two classes -- the CDrawing2BitmapDlg class (declared in "Drawing2BitmapDlg.h" and implemented in "Drawing2BitmapDlg.cpp") is the dialog-based application's nerve center while the CPath class (declared in "Path.h" and implemented in "Path.cpp") represents a drawing segment.

Creating the drawing

The drawing in the demo program consists of a collection of paths. Each path is an array of serially connected CPoint objects. When the user clicks the drawing area (represented by a static control), a new path is created. Points are then added to the new path as the user drags the mouse. The current drawing is updated as the user adds points to the current path. Path creation is complete as soon as the user releases the mouse. This process is repeated to add new paths to the drawing.

The CPath class encapsulates a segment of a drawing. It is declared in the "Path.h" file and implemented in the "Path.cpp" file. Following is the declaration of the CPath class.

//A path is a collection of serailly connected points
//The points are stored as CPoint objects in the m_paPointList array
class CPath  
{
public:
    //Default constructor. Creates an empty path
    CPath();
    //Draws the points in the order in which they were added using the 
    //currently selected pen or other drawing object
    BOOL Draw(CDC* pDC);
    //Adds a new point to the path. Return TRUE if the pPoint input parameter
    //is not NULL, FALSE otherweise
    BOOL AddPoint(CPoint* pPoint);
    //Retrieves the point at the given index. The index must be greater than
    //or equal to zero and less han the current number of points in the path.
    //Returns a pointer to the indicated CPoitn object is the index is valid,
    //NULL, otherwise
    CPoint* GetPointAt(int nIndex);
    //Returns the number of points in this path
    int GetPointCount();
    //Default destructor. Deletes the points in this path
    virtual ~CPath();
protected:
    CPtrArray m_paPointList;
};

Within the CDrawing2BitmapDlg class, path creation takes place in the mouse event message handlers -- OnLButtonDown(), OnMouseMove() and OnLButtonUp():

void CDrawing2BitmapDlg::OnLButtonDown(UINT nFlags, CPoint point) 
{
    //See if the mouse is within the drawing surface
    if(m_rDrawingSurface.PtInRect(point) == TRUE)
    {
        //Create a new path and add it to our path list
        m_pCurrentPath = new CPath;
        if(m_pCurrentPath != NULL)
        {
            //////////////////////////////////////////////////
            m_paPathList.Add(m_pCurrentPath);
            //////////////////////////////////////////////////
            m_bIsCreatingPath = TRUE;
            SetCapture();
            CRect rClip;
            DrawingSurface.GetWindowRect(&rClip);
            ClipCursor(&rClip);
        }
    }
    CDialog::OnLButtonDown(nFlags, point);
}


void CDrawing2BitmapDlg::OnMouseMove(UINT nFlags, CPoint point) 
{
    if((m_bIsCreatingPath == TRUE) && (m_pCurrentPath != NULL))
    {
        //Add this point to our current path
        CPoint* pPoint = new CPoint(point);
        if(pPoint != NULL)
        {
            //Translate the point to fit within the drawing surface
            pPoint->Offset(-m_rDrawingSurface.left, -m_rDrawingSurface.top);
            m_pCurrentPath->AddPoint(pPoint);
        }
        //Then update the drawing
        Draw();
    }
    CDialog::OnMouseMove(nFlags, point);
}


void CDrawing2BitmapDlg::OnLButtonUp(UINT nFlags, CPoint point) 
{
    if(m_bIsCreatingPath == TRUE)
    {
        //Release the mouse and input focus
        m_bIsCreatingPath = FALSE;
        ReleaseCapture();
        ClipCursor(NULL);
    }
    CDialog::OnLButtonUp(nFlags, point);
}

Painting the drawing to screen

The location of the static control -- DrawingSurface -- used to represent the drawing area is calculated in the OnInitDialog() member function of the CDrawing2BitmapDlg class. In the OnInitDialog() function implementation, the size of DrawingSurface control is used to create a bitmap (DIB section) to hold the drawing. Actual drawing of the current collection of paths takes place in the Draw() member function. First, a memory device context is created. Next, the bitmap that we wish to draw to is selected into the memory device context. Finally, the background color and the current path collection are drawn to the memory device context which is in turn blotted to the static control representing the drawing surface.

Here is the code that creates the bitmap (in the OnInitDialog function) and does the actual drawing (in the Draw function):

BOOL CDrawing2BitmapDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    // Add "About..." menu item to system menu.

    // IDM_ABOUTBOX must be in the system command range.
    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX < 0xF000);

    CMenu* pSysMenu = GetSystemMenu(FALSE);
    if (pSysMenu != NULL)
    {
        CString strAboutMenu;
        strAboutMenu.LoadString(IDS_ABOUTBOX);
        if (!strAboutMenu.IsEmpty())
        {
            pSysMenu->AppendMenu(MF_SEPARATOR);
            pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
        }
    }

    // Set the icon for this dialog.  The framework does this automatically
    //  when the application's main window is not a dialog
    SetIcon(m_hIcon, TRUE);         // Set big icon
    SetIcon(m_hIcon, FALSE);        // Set small icon
    /////////////////////////////////////////////////////////
    //Get the drawing surface and create a corresponding 
    //bitmap with the same dimensions
    DrawingSurface.GetWindowRect(&m_rDrawingSurface);
    ScreenToClient(&m_rDrawingSurface);
    CDC* pDC = GetDC();
    if(pDC != NULL)
    {
        BMIH.biSize = sizeof(BITMAPINFOHEADER);
        BMIH.biBitCount = 24;
        BMIH.biPlanes = 1;
        BMIH.biCompression = BI_RGB;
        BMIH.biWidth = m_rDrawingSurface.Width();
        BMIH.biHeight = m_rDrawingSurface.Height();
        BMIH.biSizeImage = ((((BMIH.biWidth * BMIH.biBitCount) 
                           + 31) & ~31) >> 3) * BMIH.biHeight;
        m_hDrawingSurface = CreateDIBSection(pDC->GetSafeHdc(), 
                 (CONST BITMAPINFO*)&BMIH, DIB_RGB_COLORS, 
                 (void**)&m_pDrawingSurfaceBits, NULL, 0);
        ReleaseDC(pDC);
    }
    ////////////////////////////////////////////////////////
    if((m_hDrawingSurface == NULL) || 
       (m_pDrawingSurfaceBits == NULL))
    {
        //We could not create the bitmap -- quit
        AfxMessageBox(IDS_BITMAP_NOT_CREATED_ERROR_MESSAGE, 
                                      MB_OK | MB_ICONSTOP);
        PostQuitMessage(0);
    }
    ////////////////////////////////////////////////////////
    return TRUE;
    // return TRUE  unless you set the focus to a control
}


void CDrawing2BitmapDlg::Draw()
{
    CDC* pDC = GetDC();
    if(pDC != NULL)
    {
        CDC dcMem;
        //Create a memory dc and select the drawing surface into it
        if(dcMem.CreateCompatibleDC(pDC) == TRUE)
        {
            HBITMAP hOldBitmap = 
              (HBITMAP)SelectObject(dcMem.GetSafeHdc(), 
              m_hDrawingSurface);
            /////////////////////////////////////////////
            //Once out drawing surface has been selected 
            //into the memory dc, we can draw anything 
            //and have it all nicely collected 
            //in our bitmap for future use
            //Fill up the background
            dcMem.FillSolidRect(0, 0, m_rDrawingSurface.Width(), 
                   m_rDrawingSurface.Height(), m_crBackgroundColor);
            //Now create and select the pen into 
            //the memory dc, extract the paths and draw them
            CPen ThePen(m_nPenStyle, m_nPenWidth, m_crPenColor);
            CPen* pOldPen = dcMem.SelectObject(&ThePen);

            int nPathCount = m_paPathList.GetSize();
            for(int i = 0; i < nPathCount; i++)
            {
                CPath* pPath = (CPath*)m_paPathList.GetAt(i);
                if(pPath != NULL)
                    pPath->Draw(&dcMem);
            }
            dcMem.SelectObject(pOldPen);
            //Now blit our memory drawing to the static control's rectangle
            BOOL bResult = BitBlt(pDC->GetSafeHdc(), 
                                  m_rDrawingSurface.left,
                                  m_rDrawingSurface.top,
                                  m_rDrawingSurface.Width(),
                                  m_rDrawingSurface.Height(),
                                  dcMem.GetSafeHdc(),
                                  0,
                                  0,
                                  SRCCOPY);
            //And deselect the drawing surface
            SelectObject(dcMem.GetSafeHdc(), hOldBitmap);
            //////////////////////////////////////////////////
        }
        ReleaseDC(pDC);
    }
}

Saving the drawing to a bitmap file

When the user clicks the "Save..." button, the OnSave function is called to have the bitmap representing the drawing saved to a Windows bitmap file. The following source code illustrates:

void CDrawing2BitmapDlg::OnSave() 
{
    CString szFilter;
    szFilter.LoadString(IDS_WINDOWS_BITMAP_FILES);
    //Display the "Save As" dialog for the user to specify a path name
    CFileDialog dlg(FALSE, DEFAULT_BITMAP_FILE_EXTENSION, 
        DEFAULT_BITMAP_FILE_NAME, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, 
        szFilter, NULL);
    if(dlg.DoModal() == IDOK)
    {
        CString szPathName = dlg.GetPathName();
        //Create a new file for writing
        FILE *pFile = fopen(szPathName, "wb");
        if(pFile == NULL)
        {
            AfxMessageBox(IDS_FILE_CREATE_ERROR_MESSAGE);
            return;
        }
        BITMAPFILEHEADER bmfh;
        int nBitsOffset = sizeof(BITMAPFILEHEADER) + BMIH.biSize; 
        LONG lImageSize = BMIH.biSizeImage;
        LONG lFileSize = nBitsOffset + lImageSize;
        bmfh.bfType = 'B'+('M'<<8);
        bmfh.bfOffBits = nBitsOffset;
        bmfh.bfSize = lFileSize;
        bmfh.bfReserved1 = bmfh.bfReserved2 = 0;
        //Write the bitmap file header
        UINT nWrittenFileHeaderSize = fwrite(&bmfh, 1, 
                     sizeof(BITMAPFILEHEADER), pFile);
        //And then the bitmap info header
        UINT nWrittenInfoHeaderSize = fwrite(&BMIH, 
               1, sizeof(BITMAPINFOHEADER), pFile);
        //Finally, write the image data itself 
        //-- the data represents our drawing
        UINT nWrittenDIBDataSize = 
             fwrite(m_pDrawingSurfaceBits, 1, lImageSize, pFile);

        fclose(pFile);
    }
}

Conclusion

The technique described here is a simple way to convert a drawing into a bitmap file. Although a simple collection of paths has been used to illustrate the technique, any drawing could be saved to a bitmap file using this technique.

Sophisticated results can be obtained by applying this technique to more complex drawings. Cute Skin -- a professional application skinning framework allowing you to generate visually stunning professional applications at the click of a button uses this technique to create full 32-bit alpha-channel capable icons. You can download a free trial copy of Cute Skin from our website.

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
President Imatronics Corporation
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionHow to save the Drawing in JPEG Pin
shaima'3-Jan-15 1:31
shaima'3-Jan-15 1:31 
Generalexcellent Pin
edwardking25-Nov-14 6:14
edwardking25-Nov-14 6:14 
QuestionHow to save an image without having the path bits? Pin
Michael B Pliam9-Jun-12 11:35
Michael B Pliam9-Jun-12 11:35 
Questionhow to draw box on typed characters Pin
74yrsold27-Aug-08 20:25
74yrsold27-Aug-08 20:25 
Questionhow to copy the bitmap data to clipboard? [modified] Pin
Rockone10-Jan-07 18:21
Rockone10-Jan-07 18:21 
GeneralLoad Bitmap Pin
asef9-Oct-06 1:58
asef9-Oct-06 1:58 
Hi there!
How can I load a bitmap with fread function to a static.

Thanks alotSmile | :)

MOSTAFA

GeneralChange Pen features Pin
Cauro20-Feb-06 5:36
Cauro20-Feb-06 5:36 
GeneralCould you explain a little bit on the image size calculation Pin
amonlee19-Nov-05 19:25
amonlee19-Nov-05 19:25 
GeneralRe: Could you explain a little bit on the image size calculation Pin
Samit Sasan1-Mar-07 1:50
Samit Sasan1-Mar-07 1:50 
GeneralnWrittenFileHeaderSize variable Pin
Alex Cutovoi11-Oct-05 6:37
Alex Cutovoi11-Oct-05 6:37 
Questionhow to open bitmap file? Pin
AgnesChiu18-Jul-05 17:57
AgnesChiu18-Jul-05 17:57 
AnswerRe: how to open bitmap file? Pin
fan wei fang15-Dec-05 23:18
fan wei fang15-Dec-05 23:18 
AnswerRe: how to open bitmap file? Pin
Hamid_RT31-Aug-07 3:59
Hamid_RT31-Aug-07 3:59 
GeneralDrawing in OnPaint() Pin
Member 170446316-Feb-05 18:14
Member 170446316-Feb-05 18:14 
Generalbug Pin
F5Tiger14-Jan-05 3:19
F5Tiger14-Jan-05 3:19 
GeneralRe: bug Pin
Code@Imatronics18-Jan-05 21:57
Code@Imatronics18-Jan-05 21: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.