Creating HBITMAP from memory buffer
Asked Answered
P

3

13

I have an application which loads some blob data out of a database which can represent png formatted or raw binary data for various bitmaps and icons. This is being stored in a std::vector<unsigned char>

I'm using CImageList objects to display various images in tree views, toolbar images, etc but the problem is creating bitmaps from the data in memory are coming out fuzzy as if it's missing pixels when doing something like below:

std::vector<unsigned char> bits;
HBITMAP hbitmap = CreateBitmap(16, 16, 1, 32, bits.data());

To work around this issue for now I am simply writing out the data() in the vector to a temporary file and then using LoadImage to read it back in and creating the HBITMAP from that. This works perfectly however that is admittedly a shameless hack and should be completely unnecessary I would hope.

I've looked around online but haven't found any really good examples of how to "properly" create hbitmaps from memory. I would like to be able to create these bitmaps to be added to the image list without any file i/o and limited amounts of copying the data around if possible.

Looking for the best way to do this and obviously windows specific code is fine.

UPDATE:

Based on jdv's answer I began playing with CreateCompatibleBitmap, CreateDIBitmap, and finally CreateDIBSection. All of these ended up creating lovely black bitmaps instead of the previous fuzzy bitmaps so I must be doing something wrong again and my guess is since this bitmap creation is being done in an object that has no concept of the screen dc or window that using GetDC(NULL) and CreateCompatibleDC(NULL) are no good. Sample code:

    BITMAPINFO bmi;
    ZeroMemory(&bmi, sizeof(BITMAPINFO));
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biBitCount = 32;
    bmi.bmiHeader.biHeight = 16;
    bmi.bmiHeader.biWidth = 16;
    bmi.bmiHeader.biPlanes = 1;

    HDC dc = CreateCompatibleDC(NULL);
    HBITMAP hbitmap = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS, (void**)blobData.GetMember<FILEDATAFIELD_DATA>().data(), NULL, 0);

I am now of course thinking there has got to be a simpler way to go about this perhaps by avoiding the HBITMAP altogether and working directly with CBitmap class? When it comes down to adding the image to the CImageList I'm using CBitmap::FromHandle(HBITMAP hbitmap, COLORREF mask) anyway. Does anyone know a simple way to initialize a CBitmap object from a std::vector<unsigned char>?

Polyploid answered 4/1, 2011 at 21:56 Comment(2)
Please don't edit a the question to a new meaning as it invalidates the answers. Feel free to post a new question and link to the old one.Gothicism
Sorry just took a step back and realized the only reason I was going into GDI land was because I was using the FromHandle method when adding to the CImageList ... MFC CBitmap simply wraps the GDI calls anyway so it's very similar.Polyploid
P
4

Using GdiPlus I got something that works pretty well and doesn't involve pulling any teeth!

Gdiplus::Bitmap* pBitmap = NULL;
IStream* pStream = NULL;

HRESULT hResult = ::CreateStreamOnHGlobal( NULL, TRUE, &pStream );
if(hResult == S_OK && pStream)
{
    hResult = pStream->Write(&bits[0], ULONG(bits.size()), NULL);
    if(hResult == S_OK)
        pBitmap = Gdiplus::Bitmap::FromStream(pStream);
    pStream->Release();
}

Edit: Changed per Jegatheesh

Polyploid answered 6/1, 2011 at 15:13 Comment(2)
And how obtain HBITMAP from Gdiplus::Bitmap* ?Sligo
@nick_n_a: GdipCreateHBITMAPFromBitmap?Irrelative
G
6

I'd use CreateCompatibleBitmap, and then call SetDIBits to fill it with your data. These are functions I have seen to work, and SetDIBits is quite flexible, although verbose.

In my MFC years, CreateBitmap was avoided due to suspected performance issues.

Gothicism answered 4/1, 2011 at 22:22 Comment(1)
I'm looking at CreateDIBitmap now which from what I can tell basically creates a DC compatible bitmap and then calls an equivalent of SetDIBits if you specify the CBM_INIT flag. Whoever named parameters and members of structs in windows API needs to be shot.Polyploid
P
4

Using GdiPlus I got something that works pretty well and doesn't involve pulling any teeth!

Gdiplus::Bitmap* pBitmap = NULL;
IStream* pStream = NULL;

HRESULT hResult = ::CreateStreamOnHGlobal( NULL, TRUE, &pStream );
if(hResult == S_OK && pStream)
{
    hResult = pStream->Write(&bits[0], ULONG(bits.size()), NULL);
    if(hResult == S_OK)
        pBitmap = Gdiplus::Bitmap::FromStream(pStream);
    pStream->Release();
}

Edit: Changed per Jegatheesh

Polyploid answered 6/1, 2011 at 15:13 Comment(2)
And how obtain HBITMAP from Gdiplus::Bitmap* ?Sligo
@nick_n_a: GdipCreateHBITMAPFromBitmap?Irrelative
B
1

A slight variation on the answer provided by AJG85, that employs SHCreateMemStream in place of CreateStreamOnHGlobal. SHCreateMemStream was introduced as a public API in Windows Vista, and provides the benefit of creating an IStream over an existing region in memory, thus avoiding an additional memory allocation:

#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();
}

This implementation either throws a _com_error, or returns an HBITMAP that refers to the image constructed from the in-memory data.

When the function returns, the memory buffer can be safely freed. The returned HBITMAP is owned by the caller, and needs to be released with a call to DeleteObject.

Businesslike answered 6/7, 2020 at 10:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.