【问题标题】:Creating HBITMAP from memory buffer从内存缓冲区创建 HBITMAP
【发布时间】:2011-06-03 16:30:55
【问题描述】:

我有一个应用程序可以从数据库中加载一些 blob 数据,这些数据可以表示各种位图和图标的 png 格式或原始二进制数据。这是存储在std::vector<unsigned char>

我正在使用CImageList 对象在树视图、工具栏图像等中显示各种图像,但问题是从内存中的数据创建位图变得模糊,就好像它在执行以下操作时缺少像素一样:

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

为了暂时解决这个问题,我只是将向量中的 data() 写入一个临时文件,然后使用 LoadImage 将其读回并从中创建 HBITMAP。这非常有效,但是这无疑是一种无耻的黑客行为,我希望完全没有必要。

我在网上四处查看,但没有找到任何关于如何“正确”从内存中创建 hbitmap 的非常好的示例。我希望能够创建这些位图以添加到图像列表中,而无需任何文件 i/o 并且尽可能地复制有限数量的数据。

寻找执行此操作的最佳方法,显然 Windows 特定代码很好。

更新:

根据 jdv 的回答,我开始使用 CreateCompatibleBitmap、CreateDIBitmap,最后是 CreateDIBSection。所有这些最终都创建了可爱的黑色位图,而不是以前的模糊位图,所以我一定又做错了,我的猜测是因为这个位图创建是在一个没有屏幕 dc 或使用 @ 的窗口概念的对象中完成的987654324@ 和 CreateCompatibleDC(NULL) 不好。示例代码:

    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);

我现在当然认为必须有一种更简单的方法来解决这个问题,也许完全避免 HBITMAP 并直接使用 CBitmap 类?当涉及到将图像添加到CImageList 时,无论如何我都在使用CBitmap::FromHandle(HBITMAP hbitmap, COLORREF mask)。有谁知道从std::vector&lt;unsigned char&gt; 初始化CBitmap 对象的简单方法?

【问题讨论】:

  • 请不要将问题编辑为新含义,因为它会使答案无效。随意发布一个新问题并链接到旧问题。
  • 抱歉,我只是后退了一步,意识到我进入 GDI 领域的唯一原因是因为我在添加到 CImageList 时使用了 FromHandle 方法......无论如何,MFC CBitmap 只是简单地包装了 GDI 调用,所以非常相似。

标签: c++ mfc bitmap gdi hbitmap


【解决方案1】:

我会使用CreateCompatibleBitmap,然后调用SetDIBits 用您的数据填充它。这些是我见过的函数,SetDIBits 非常灵活,虽然很冗长。

在我的 MFC 岁月中,CreateBitmap 因疑似性能问题而被回避。

【讨论】:

  • 我现在正在查看 CreateDIBitmap,据我所知,它基本上创建了一个 DC 兼容位图,然后如果您指定 CBM_INIT 标志,则调用等效的 SetDIBits。在 windows API 中命名参数和结构成员的人需要被枪决。
【解决方案2】:

使用 GdiPlus,我得到了一些效果很好且不涉及拔牙的东西!

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

编辑:根据 Jegatheesh 更改

【讨论】:

  • 如何从Gdiplus::Bitmap*获取HBITMAP?
  • @nick_n_a: GdipCreateHBITMAPFromBitmap?
【解决方案3】:

AJG85 提供的answer 略有不同,它使用SHCreateMemStream 代替CreateStreamOnHGlobalSHCreateMemStream 是作为 Windows Vista 中的公共 API 引入的,它提供了在内存中的现有区域上创建 IStream 的好处,从而避免了额外的内存分配:

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

这个实现要么抛出一个_com_error,要么返回一个HBITMAP,它引用了从内存数据构造的图像。

当函数返回时,可以安全地释放内存缓冲区。返回的HBITMAP 归调用者所有,需要通过调用DeleteObject 来释放。

【讨论】:

    猜你喜欢
    • 2012-05-14
    • 2014-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-25
    • 1970-01-01
    相关资源
    最近更新 更多