0

I have a void pointer to data that was read from a *.bmp file. The bmp file no longer exists (there is now one file that contains hundreds of these bitmap file datasets). How can I initialize an MFC CBitmap using this data?

I see the CBitmap::Create* functions (e.g., CreateBitmap(), CreateCompatibleBitmap(), etc), but they require that I know the bitmap's height and width, can get access to the data bits, etc. I can write the data to disk and then use ::LoadImage() and CBitmap::Attach() to load the bitmap, but I want to do this in memory to improve performance.

Thanks!

UPDATE (#2):

Here is my code, as suggested and simplified by Constantine Georgiou's comments and post (thank you!). CBitmap::CreateBitmap() no longer fails, but the bitmap displays as black.

// Bitmap File Header
LPBITMAPFILEHEADER pFileHdr = (LPBITMAPFILEHEADER)pFileData;
// Bitmap Info Header
LPBITMAPINFOHEADER pBmpHdr = (LPBITMAPINFOHEADER)((PCHAR)pFileData + sizeof(BITMAPFILEHEADER));
// Image Data
LPVOID lpBits = (LPVOID)((PCHAR)pFileData + pFileHdr->bfOffBits);
if(!bitmap.CreateBitmap(pBmpHdr->biWidth, pBmpHdr->biHeight, pBmpHdr->biPlanes, pBmpHdr->biBitCount, lpBits))
    bool bummer = true;

Here is code that writes the same data to a file and then loads the bitmap using ::LoadImage(). This works.

CFile file;
if(file.Open(sFilename, CFile::modeCreate | CFile::modeReadWrite))
{
    file.Write(pFileData, dwFileBytes);
    file.Close();

    HBITMAP hBitmap = (HBITMAP)::LoadImage(NULL, sFilename, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
    if(hBitmap)
    {
        if(!bitmap.Attach(hBitmap))
            bool ahHeck = true;
    }
}

Here are trace messages regarding the above.

Inspecting the file data in memory before creating the image:

  • dwFileBytes = 3128.
  • BMP HEADER:
  • biSize = 40
  • biWidth = 32
  • biHeight = 32
  • biPlans = 1
  • biBitCount = 24
  • biCompression = 0
  • biSizeImage = 3074

Inspecting BITMAP after calling CreateBitmap(32, 32, 1, 24, lpBits):

  • bmType = 0
  • bmWidth = 32
  • bmHeight = 32
  • bmPlanes= 1
  • bmWidthBytes = 96
  • bmBitsPixel = 24
  • bmBits = 0

(This bitmap displays as black.)

Inspecting BITMAP after writing to file and calling LoadImage():

  • bmType = 0
  • bmWidth = 32
  • bmHeight = 32
  • bmPlanes= 1
  • bmWidthBytes = 128
  • bmBitsPixel = 32
  • bmBits = 0

(This bitmap displays correctly.)

I realize I'm getting into the weeds with details. Apologies! I'm stumped.

Steve A
  • 1,798
  • 4
  • 16
  • 34
  • 1
    Don't quite understand, you did have the .bmp file before, and you read it. If, instead of reading the file into the memory, you called LoadImage(), wouldn't you have the image data available? Otherwise, you can use the data in the memory block. .bmp files contain a [BITMAPFILEHEADER](https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapfileheader) structure at the beginning of the file, with a `BITMAPINFO` structure following immediately, so you could use these. – Constantine Georgiou Jul 02 '20 at 14:15
  • Constantine, the file was read months ago and the data stored within another file (that contained multiple bitmap files. Yes, as mentioned, I can save the bytes and LoadImage() it into a CBitmap, but I can't afford the disk time (there are 500 of these images). Not sure how I'd get the BITMAPFILEHEADER from the bytes I have... I'll look into it. – Steve A Jul 02 '20 at 18:35
  • Bitmap width has to be aligned to multiples of 4 bytes, not bits. The file header is almost always 54 bytes, but in rare cases it can be different. Use APIs to get these information. – Barmak Shemirani Jul 03 '20 at 13:33

2 Answers2

1

Your code is correct, although I would rather simplify it a little - no need to make copies of these structures, as they are already there - just some typecasting and pointer arithmetic. For example:

// Bitmap File Header
LPBITMAPFILEHEADER pFileHdr = (LPBITMAPFILEHEADER)pFileData;

// Bitmap Info Header
LPBITMAPINFOHEADER pBmpHdr = (LPBITMAPINFOHEADER)((PCHAR)pFileData + sizeof(BITMAPFILEHEADER));

// Image Data
LPVOID lpBits = (LPVOID)((PCHAR)pFileData + pFileHdr->bfOffBits);

The documentatiom clearly mentions that "bit count" is the number of bits per pixel. So, the call would be:

if (!m_IconBitmap.CreateBitmap(pBmpHdr->biWidth, pBmpHdr->biHeight, pBmpHdr->biPlanes, 
    pBmpHdr->biBitCount, lpBits))
{
    // Handle the Error
}

(haven't really tested this code, but it should work)

I have to mention though that the image format is rather unusual, ie 16 bits per pixel is probably a BGR 5-5-5 format (5 bits for each component). And with two bytes per pixel, the image size should rather be 2048 bytes (32 x 32 x 2). A bitmap file like this, created by Microsoft tools would be 2102 bytes exactly, which is correct (54 bytes for the structures plus 2048 for the bitmap data - no palette). Maybe you should store the data to a file and examine them with a hex editor.

Constantine Georgiou
  • 2,412
  • 1
  • 13
  • 17
  • *"typecasting and pointer arithmetic"*, while common, simply lead to undefined behavior in C++. The copies in the question actually *do* serve a purpose. [std::bit_cast](https://en.cppreference.com/w/cpp/numeric/bit_cast) is the future solution to the issue. – IInspectable Jul 03 '20 at 10:55
  • Constantine, I updated my post based on your simplifications (thank you!). The update explains how the image appears as black and has trace data. FWIW, some of the images are 16 bit and others 24. It is a shame SO doesn't allow private messages, as I would be happy to connect with you on Freelancer. – Steve A Jul 03 '20 at 14:30
  • 1
    I don't know why these bitmaps are not displayed, but I suspect that's because they are DIB (Device-Independent Bitmaps), while `LoadImage()` creates DDB (Device-Independent Bitmaps). DIBs aren't drawn using `BitBlt()`, you need to use `SetDIBitsToDevice()` instead. If the painting code cannot or is hard to be changed, you will have to convert the DIB to DDB: create a DDB and a memory DC, select it into the mem DC, paint (`SetDIBitsToDevice()`) the DIB there, and finally select the DDB out of the mem DC - it will have received the updates, and be available for painting using the DBB routines. – Constantine Georgiou Jul 03 '20 at 18:18
0

If using GDI+ is an option, you can construct an HBITMAP from an in-memory data buffer using essentially this answer, and finally attaching the returned HBITMAP to an MFC CBitmap. This is the original code:

#include <Shlwapi.h>
#include <atlimage.h>
#include <comdef.h>
#include <comip.h>

#include <vector>

#pragma comment(lib, "Shlwapi.lib")
#if defined(_DEBUG)
#    pragma comment(lib, "comsuppwd.lib")
#else
#    pragma comment(lib, "comsuppw.lib")
#endif


HBITMAP from_data(std::vector<unsigned char> const& data)
{
    if (data.empty())
    {
        _com_issue_error(E_INVALIDARG);
    }

    auto const stream { ::SHCreateMemStream(&data[0], static_cast<UINT>(data.size())) };
    if (!stream)
    {
        _com_issue_error(E_OUTOFMEMORY);
    }
    _COM_SMARTPTR_TYPEDEF(IStream, __uuidof(IStream));
    IStreamPtr sp_stream { stream, false };

    CImage img {};
    _com_util::CheckError(img.Load(sp_stream));

    return img.Detach();
}

And here is a snippet on how to attach the returned HBITMAP to a CBitmap:

#include <afxwin.h>

// ...

CBitmap bitmap {};
bitmap.Attach(from_data(data));
IInspectable
  • 46,945
  • 8
  • 85
  • 181
  • That worked well! I needed to do some tweaking to change the use of a vector, but it was awesome. Thanks! – Steve A Jul 13 '20 at 19:59