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

Saving a Drawing to a Bitmap File

0.00/5 (No votes)
18 Jan 2005 1  
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