Home > 2. Windows Programming, Native Code > Serialize and deserialize BITMAP object in MFC/Win32

Serialize and deserialize BITMAP object in MFC/Win32

Download the source code for various platforms: WinCE/WinMobile, Win32 API

Update Feb 10, 2011: The source code I uploaded is for WinMobile platform. Today I have modified and upload the Win32 version. All the source code inside this post is also changed to Win32 version.

Lately, in one of my project, I have to save an BITMAP object into an XML document. The problem is I can only save an CString into the XML document. So I have to find a way to convert BITMAP data (in a HBITMAP) into CString.

It’s pretty easy if I serialize the BITMAP to an byte[] array, and then convert the result byte[] array into CString format. So I will consequently discuss on these 2 problems.

1. Convert HBITMAP to byte[] array, and vice versa:

Generally, an *.BMP image file in Windows is organized in 3 chunks, as following:

|   BITMAPFILEHEADER | BITMAPINFO | Bitmap raw data

Using this format, Windows can detect and display (read/write) bitmap file, because it contains all of information that Windows need to know about the bitmap.

So the idea is simple, from the given HBITMAP, I use GetObject(), GetDIBits()… inoder to have all of information about the image. Using GetDIBits() is a little bit tricky, but if you read the following code carefully, it’s easy to understand.

static char* Bitmap2Bytes(HBITMAP hBitmap, int& len)
 {
 BITMAP bmpObj;
 HDC hDCScreen;
 int iRet;
 DWORD dwBmpSize;

 BITMAPFILEHEADER    bmfHeader;
 LPBITMAPINFO        lpbi;
 const DWORD dwSizeOfBmfHeader = sizeof(BITMAPFILEHEADER);
 DWORD dwSizeOfBmInfo = sizeof(BITMAPINFO);

 hDCScreen = GetDC(NULL);
 GetObject(hBitmap, sizeof(BITMAP), &bmpObj);

 HGLOBAL hDIB = GlobalAlloc(GHND, dwSizeOfBmInfo + 8);
 lpbi = (LPBITMAPINFO)GlobalLock(hDIB);
 lpbi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);

 // Gets the "bits" from the bitmap and copies them into a buffer
 // which is pointed to by lpbi
 iRet = GetDIBits(hDCScreen, hBitmap, 0,
 (UINT)bmpObj.bmHeight,
 NULL,
 lpbi, DIB_RGB_COLORS);
 ASSERT(iRet > 0);

 // only 16 and 32 bit images are supported.
 ASSERT(lpbi->bmiHeader.biBitCount == 16 || lpbi->bmiHeader.biBitCount == 32);
 if(lpbi->bmiHeader.biCompression == BI_BITFIELDS)
 dwSizeOfBmInfo += 8;

 dwBmpSize = lpbi->bmiHeader.biSizeImage;
 HGLOBAL hDIBBmData = GlobalAlloc(GHND, dwBmpSize);
 char* lpbitmap = (char*)GlobalLock(hDIBBmData);

 iRet = GetDIBits(hDCScreen, hBitmap, 0,
 (UINT)bmpObj.bmHeight,
 lpbitmap,
 lpbi, DIB_RGB_COLORS);
 ASSERT(iRet > 0);

 DWORD dwSizeofDIB   =    dwBmpSize + dwSizeOfBmfHeader + dwSizeOfBmInfo;
 bmfHeader.bfOffBits = (DWORD)dwSizeOfBmfHeader + (DWORD)dwSizeOfBmInfo;
 bmfHeader.bfSize = dwSizeofDIB;
 bmfHeader.bfType = 0x4D42; //BM
 bmfHeader.bfReserved1 = bmfHeader.bfReserved2 = 0;

 char* arrData = new char[dwSizeofDIB];
 memcpy(arrData, &bmfHeader, dwSizeOfBmfHeader);
 memcpy(arrData + dwSizeOfBmfHeader, lpbi, dwSizeOfBmInfo);
 memcpy(arrData + dwSizeOfBmfHeader + dwSizeOfBmInfo, lpbitmap, dwBmpSize);

 GlobalUnlock(hDIB);
 GlobalUnlock(hDIBBmData);
 GlobalFree(hDIB);
 GlobalFree(hDIBBmData);
 ReleaseDC(NULL, hDCScreen);

 len = dwSizeofDIB;
 return arrData;
 }

The vice versa is similar, but in this case, we use SetDIBits(), instead of GetDIBits():

static HBITMAP Bytes2Bitmap(char arrData[], int iLen)
 {
 PBITMAPFILEHEADER    bmfHeader;
 PBITMAPINFO    pbi;
 HDC            hDC;
 HBITMAP        hBmpRet;
 int            iRet;
 char        *lpbitmap;
 int            iSizeOfBmInfo;
 const int    iSizeOfBmfHeader = sizeof(BITMAPFILEHEADER);

 // get the BITMAPFILEHEADER
 bmfHeader = (PBITMAPFILEHEADER)arrData;
 arrData += iSizeOfBmfHeader;

 // get the BITMAPINFO
 iSizeOfBmInfo = bmfHeader->bfOffBits - iSizeOfBmfHeader;
 pbi = (PBITMAPINFO)arrData;
 arrData += iSizeOfBmInfo;

 ASSERT(pbi->bmiHeader.biSizeImage == iLen - iSizeOfBmfHeader - iSizeOfBmInfo);

 // create the output DDB bitmap
 hDC = GetDC(NULL);
 hBmpRet = CreateCompatibleBitmap(hDC,
 pbi->bmiHeader.biWidth, pbi->bmiHeader.biHeight);
 ASSERT(hBmpRet);

 // set the image...
 iRet = SetDIBits(hDC, hBmpRet, 0,
 pbi->bmiHeader.biHeight,
 arrData,
 pbi, DIB_RGB_COLORS);
 ASSERT(iRet > 0);

 ::ReleaseDC(NULL, hDC);

 return hBmpRet;
 }

2. Byte[] array into CString, and vice versa:

The easiest solution you can think about is simply cast the byte[] array into wchar_t[] array (or something like that), and then construct the CString object. Another way is use mbstowcs() to convert byte[] array to wide char format.

Unfortunately, both of these solutions will not work if there is some zero byte in the array, because zero bytes (aka ”, the character whose ASCII code is 0) is used as the terminal character  in the string. Since our byte[] array is bitmap data, so we can’t be sure if it has zero bytes or not.

Well, remember about the Image Processing classes I have attended this semester. If you had attend a similar course, you have to know RLE, one of the simplest image encoding method.

Use RLE, we can easily overcome the “zero bytes”. For instance, we have this byte[] array:

byte arr[] = { 1, 1, 1, 0, 0, 0, 4 }

after encode it using RLE, we will have:

byte encoded[] = {3, 1, 2, 0, 1, 4}

This means we never have an zero byte succeed by another zero byte (i.e. 2 zero bytes stand side-by-side). This makes sense because we’re converting the byte[] array into a wide-char string, so the result string will not contain any terminal character.

In the following functions, I use MAKEWORD, HIBYTE and LOWBYTE macro from MFC. It makes the code easier to read.

// use RLE to encode byte array into CString format
static CString Bytes2String(char* arrBytes, int len)
 {
 ASSERT(len > 0);
 ASSERT(arrBytes);

 wchar_t* strResult;    // maximum length
 int iResultCount;
 char bLastByte;
 int bViewedBytes;

 strResult = new wchar_t[len + 1];    // maximum length
 ZeroMemory(strResult, 2*len);
 iResultCount = 0;
 bLastByte = arrBytes[0];
 bViewedBytes = 0;

 for(int i = 0; i <= len; ++i)
 {
 if(i == len)
 {
 strResult[iResultCount++] = MAKEWORD(bLastByte, bViewedBytes);
 }
 else
 {
 if(arrBytes[i] == bLastByte)
 {
 ++bViewedBytes;
 if(bViewedBytes == 255)
 {
 strResult[iResultCount++] = MAKEWORD(bLastByte, bViewedBytes);
 bViewedBytes = 0;
 }
 }
 else
 {
 strResult[iResultCount++] = MAKEWORD(bLastByte, bViewedBytes);
 bViewedBytes = 1;
 bLastByte = arrBytes[i];
 }
 }
 }
 strResult[iResultCount] = 0;
 CString sRet(strResult);

 float f = (float)iResultCount / len;
 delete[] strResult;
 return sRet;
 }


 static int String2Bytes(CString str, char* &arrBytes)
 {
 wchar_t* arrSrc = str.GetBuffer();
 int iSrcLen = str.GetLength();
 int iDstLen = 0;

 for(int i = 0; i < iSrcLen; ++i)
 {
 iDstLen += HIBYTE(arrSrc[i]);
 }

 arrBytes = new char[iDstLen];
 int iDstByteCnt = 0;
 for(int i = 0; i < iSrcLen; ++i)
 {
 int iCnt = HIBYTE(arrSrc[i]);
 char bChar = LOBYTE(arrSrc[i]);
 for(int j = 0; j < iCnt; ++j, ++iDstByteCnt)
 {
 arrBytes[iDstByteCnt] = bChar;
 }
 }
 str.ReleaseBuffer();

 return iDstByteCnt;
 }

The remaining jobs is pretty simple, we will use 4 functions above to convert HBITMAP to CString, and vice versa, of course.

static CString SaveImage(HBITMAP hBitmap)
 {
 int len;
 char* arrData = Bitmap2Bytes(hBitmap, len);
 CString sRet = Bytes2String(arrData, len);

 delete[] arrData;

 return sRet;
 }

 static HBITMAP LoadImage(CString s)
 {
 if(s.IsEmpty() || s.GetLength() <= 0)
 return NULL;

 char* arrData;

 int iLen = String2Bytes(s, arrData);
 HBITMAP hRet = Bytes2Bitmap(arrData, iLen);
 delete arrData;

 return hRet;
 }
Advertisements
  1. Anonymous
    10/02/2011 at 9:06 AM

    Unfortunately this doesn’t work in Visual Studio 2005 MFC C++ Project!

    All I get is a BLACK picture when attempting to grab my picture out of my PictureBox.

  2. phvu
    10/02/2011 at 2:41 PM

    Hi,
    I just found out that I used the original source code in a WinMobile project. In Win32, the APIs changed slightly that they doesn’t work anymore.
    I have modified the original version to make it work under Win32 environment. You can download the source code from http://cid-75f5081ae5375f06.office.live.com/self.aspx/Shared%20source%20code/DrawUtil.h. I have check and it works well on my Windows 7. Let me know if it works or not on your system 🙂
    Another option that you might want to try is using Base64 to store the byte array. However I’m not sure which option will output longer string: my proposed solution or Base64.

  3. Anonymous
    16/02/2011 at 2:02 AM

    That seemed to fix the issue.

    Had to change a few lines to get the code to compile properly in a Visual Studio 2005 MFC C++ Project:

    1. ReleaseDC(NULL, hDC);
    changed to: ::ReleaseDC(NULL, hDC);

    2. GetDC(NULL);
    changed to: ::GetDC(NULL);

    3. wchar_t* arrSrc = str.GetBuffer();
    changed to: wchar_t* arrSrc = (wchar_t*) str.GetBuffer();

    Thank you for your assistance!

    🙂

  4. phvu
    16/02/2011 at 9:51 AM

    Never mind 🙂

  5. Zsolt
    18/12/2011 at 9:19 PM

    Hi,

    Nice work. I have a problem though. When I try to use the LoadImage method I get an exeption (“Unhandled exception at 0x004bb897 in ScreenshotReader.exe: 0xC0000005: Access violation reading location 0x0224c000.”) here :
    iDstLen += HIBYTE(arrSrc[i]); in the String2Bytes method. I have no ideea why. I am using WIndows 7 and Visual Studio 2003. I noticed that arrSrc array contains japanese characters (or something like that). Could this be something regarding language encoding or so? Pls help. Thanks.

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: