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.
class CPath
{
public:
CPath();
BOOL Draw(CDC* pDC);
BOOL AddPoint(CPoint* pPoint);
CPoint* GetPointAt(int nIndex);
int GetPointCount();
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)
{
if(m_rDrawingSurface.PtInRect(point) == TRUE)
{
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))
{
CPoint* pPoint = new CPoint(point);
if(pPoint != NULL)
{
pPoint->Offset(-m_rDrawingSurface.left, -m_rDrawingSurface.top);
m_pCurrentPath->AddPoint(pPoint);
}
Draw();
}
CDialog::OnMouseMove(nFlags, point);
}
void CDrawing2BitmapDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
if(m_bIsCreatingPath == TRUE)
{
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();
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);
}
}
SetIcon(m_hIcon, TRUE);
SetIcon(m_hIcon, FALSE);
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))
{
AfxMessageBox(IDS_BITMAP_NOT_CREATED_ERROR_MESSAGE,
MB_OK | MB_ICONSTOP);
PostQuitMessage(0);
}
return TRUE;
}
void CDrawing2BitmapDlg::Draw()
{
CDC* pDC = GetDC();
if(pDC != NULL)
{
CDC dcMem;
if(dcMem.CreateCompatibleDC(pDC) == TRUE)
{
HBITMAP hOldBitmap =
(HBITMAP)SelectObject(dcMem.GetSafeHdc(),
m_hDrawingSurface);
dcMem.FillSolidRect(0, 0, m_rDrawingSurface.Width(),
m_rDrawingSurface.Height(), m_crBackgroundColor);
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);
BOOL bResult = BitBlt(pDC->GetSafeHdc(),
m_rDrawingSurface.left,
m_rDrawingSurface.top,
m_rDrawingSurface.Width(),
m_rDrawingSurface.Height(),
dcMem.GetSafeHdc(),
0,
0,
SRCCOPY);
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);
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();
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;
UINT nWrittenFileHeaderSize = fwrite(&bmfh, 1,
sizeof(BITMAPFILEHEADER), pFile);
UINT nWrittenInfoHeaderSize = fwrite(&BMIH,
1, sizeof(BITMAPINFOHEADER), pFile);
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.