如何从依赖于设备的 HBITMAP 构造 GDI+ 位图对象

How to construct a GDI+ Bitmap object from a Device-Dependent HBITMAP

本文关键字:构造 HBITMAP GDI+ 位图 对象 依赖于      更新时间:2023-10-16

我想在以下情况下使用 GDI+ 方法Image::Save()将 DDB 保存到文件中:

HBITMAP hBitmap = CreateCompatibleBitmap(hDC, 200, 200) ;
...
//hBitmap is a DDB so I need to pass an HPALETTE
Gdiplus::Bitmap(hBitmap,  ???HPALETTE???  ).Save(L"file.png", ...) ;

问题是,当位图不是与设备无关的位图时,构造函数Bitmap请求HPALETTE

我从哪里获得必要的调色板?


跟进:
其中一个答案建议将 NULL 作为HPALETTE参数传递。
这是一个这样做的工作示例。结果是纯黑白图像,所有颜色都丢失了。

#include <windows.h>
#include <gdiplus.h>
int main(){
using namespace Gdiplus ;
GdiplusStartupInput gdiplusStartupInput ;
ULONG_PTR gdiplusToken ;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL) ;
CLSID pngEncoder = {0x557cf406, 0x1a04, 0x11d3, {0x9a, 0x73, 0x00, 0x00, 0xf8, 0x1e, 0xf3, 0x2e} } ;
HDC dcHndl = CreateCompatibleDC(NULL) ;
HBITMAP hBitmap = CreateCompatibleBitmap(dcHndl, 200, 200) ;
SelectObject(dcHndl, hBitmap) ;
BitBlt(dcHndl, 0,0, 200,200, GetDC(NULL), 0,0, SRCCOPY|CAPTUREBLT) ;
Bitmap(hBitmap, NULL).Save(L"file.png", &pngEncoder) ;
}

首先(这与你的主要问题无关(:

为屏幕截图创建位图时,不要使用内存 dc,因为这会创建单色位图。这就是你得到黑白图像的主要原因(在我的电脑上,我只得到一个黑色图像(。

不要在其他函数中使用GetDC(0)。每次调用GetDC匹配都有一个匹配ReleaseDC以避免资源泄漏。

调用BitBlt后,最好从dc中选择hbitmap,因为您基本上完成了在 dc 上的绘图。

以下代码将在 Windows 10 上运行

int w = 800;
int h = 600;
HDC hdc = GetDC(HWND_DESKTOP);
HDC memdc = CreateCompatibleDC(hdc);
HBITMAP hbitmap = CreateCompatibleBitmap(hdc, w, h);
HBITMAP oldbmp = (HBITMAP)SelectObject(memdc, hbitmap);
BitBlt(memdc, 0, 0, w, h, hdc, 0, 0, SRCCOPY | CAPTUREBLT);
SelectObject(memdc, oldbmp);
Bitmap(hbitmap, NULL).Save(filename, &pngEncoder);
DeleteObject(hbitmap);
DeleteDC(memdc);
ReleaseDC(HWND_DESKTOP, hdc);

回到您关于文档的问题:

类型:调色板
GDI 调色板的句柄,用于定义位图颜色(如果 HBM 不是与设备无关的位图 (DIB(。

另外

不要将当前(或以前(选择到设备上下文中的 GDI 位图或 GDI 调色板传递给位图::FromHBITMAP 方法。

我发布的代码仅遵循一条规则,即 GDI 位图当前未在设备上下文中选择(但以前已选择(。

该文档可能适用于旧版本的 Windows。据我所知,MFC 的CImage类并不遵循所有这些规则。新的计算机显示器都是 24 位或 32 位,我不知道您如何获得它的调色板。

要按照文档进行操作,您可以使用CreateDIBSectionGetDIBits将 DDB 转换为 DIB 部分。使用Bitmap::FromHBITMAPhbitmap_dib的新 DIB 部分。这将满足所有条件:hbitmap为 dib,则未(且未(在设备上下文中选择它。

或者,Gdiplus::Bitmap还有另一种方法Bitmap::FromBITMAPINFO。如果没有调色板,则可以改用以下代码:

HDC hdc = GetDC(HWND_DESKTOP);
HDC memdc = CreateCompatibleDC(hdc);
HBITMAP hbitmap = CreateCompatibleBitmap(hdc, w, h);
HBITMAP oldbmp = (HBITMAP)SelectObject(memdc, hbitmap);
BitBlt(memdc, 0, 0, 800, 600, hdc, 0, 0, SRCCOPY | CAPTUREBLT);
SelectObject(memdc, oldbmp);
BITMAP bm;
GetObject(hbitmap, sizeof(bm), &bm);
int size = ((bm.bmWidth * bm.bmBitsPixel + 31) / 32) * 4 * bm.bmHeight;
BITMAPINFO info{ sizeof(info), bm.bmWidth, bm.bmHeight, 1, bm.bmBitsPixel, BI_RGB, size };
std::vector<char> bits(size);
GetDIBits(memdc, hbitmap, 0, bm.bmHeight, &bits[0], &info, DIB_RGB_COLORS);
Bitmap *bitmap = Bitmap::FromBITMAPINFO(&info, &bits[0]);
bitmap->Save(filename, &pngEncoder);
delete bitmap;
DeleteObject(hbitmap);
DeleteDC(memdc);
ReleaseDC(HWND_DESKTOP, hdc);

正如 CreateCompatibleBitmap 所指出的那样,如果您正在处理彩色位图,我们还可以假设hDC是非内存设备上下文(因为内存设备上下文只会创建单色位图(,并且此位图使用的调色板与此设备上下文使用的调色板相同。您可以使用GetCurrentObject方法进行查询。但是Bitmap.Bitmap(HBITMAP, HPALETTE)构造函数的备注声明:

不要将当前(或以前(选择到设备上下文中的 GDI 位图或 GDI 调色板传递给 GDI+ 位图::位图构造函数。

因此,不能直接使用当前设备上下文调色板,而是需要创建它的副本。

/// <returns>
/// Handle to palette currently selected into device context without granting ownership.
/// </returns>
_Check_return_ ::HPALETTE
Fetch_CurrentPalette(_In_ ::HDC const h_dc)
{
assert(h_dc);
::HGDIOBJ const h_palette_object{::GetCurrentObject(h_dc, OBJ_PAL)}; // not owned
assert(h_palette_object);
assert(OBJ_PAL == ::GetObjectType(h_palette_object));
//  Perform unchecked conversion of generic GDI object descriptor to GDI palette descriptor.
::HPALETTE h_current_palette{}; // not owned
{
static_assert(sizeof(h_palette_object) == sizeof(h_current_palette), "wat");
::memcpy
(
::std::addressof(h_current_palette)
,   ::std::addressof(h_palette_object)
,   sizeof(h_current_palette)
);
}
return(h_current_palette);
}
/// <returns>
/// Handle to palette copy with granting ownership.
/// </returns>
_Check_return_ ::HPALETTE
Make_PaletteCopy(_In_ ::HPALETTE const h_palette)
{
assert(h_palette);
::UINT const first_entry_index{};
::UINT entries_count{};
::LPPALETTEENTRY p_entries{};
//  Figure out how many entries palette contains.
entries_count = ::GetPaletteEntries(h_palette, first_entry_index, entries_count, p_entries);
assert(1 < entries_count);
assert(entries_count <= ::std::numeric_limits< decltype(LOGPALETTE::palNumEntries) >::max());
//  This buffer will hold palette description which contains first PALETTEENTRY as last field.
//  followed by the rest of PALETTEENTRY items.
::std::unique_ptr< ::std::uint8_t[] > const p_buffer
{
new ::std::uint8_t[sizeof(::LOGPALETTE) + (sizeof(::PALETTEENTRY) * (entries_count - 1u))]
};
//  Perform unchecked conversion of buffer pointer to palette description pointer.
::LOGPALETTE * p_description{};
{
::std::uint8_t * const p_buffer_bytes{p_buffer.get()};
static_assert(sizeof(p_buffer_bytes) == sizeof(p_description), "wat");
::memcpy
(
::std::addressof(p_description)
,   ::std::addressof(p_buffer_bytes)
,   sizeof(p_description)
);
}
//  Copy palette entries into buffer.
p_entries = static_cast< ::LPPALETTEENTRY >(p_description->palPalEntry);
::UINT const copied_entries_count
{
::GetPaletteEntries(h_palette, first_entry_index, entries_count, p_entries)
};
assert(copied_entries_count == entries_count);
//  Create palette copy.
p_description->palVersion = 0x300; // magic
p_description->palNumEntries = static_cast< ::WORD >(copied_entries_count);
::HPALETTE const h_copied_palette{::CreatePalette(p_description)}; // owned
assert(h_copied_palette);
return(h_copied_palette);
}

::HPALETTE const hPal{Make_PaletteCopy(Fetch_CurrentPalette(hDC))}; // owned
assert(hPal);
::HBITMAP const hBitmap{::CreateCompatibleBitmap(hDC, 200, 200)}; // owned
assert(hBitmap);
{
::Gdiplus::Bitmap bmp{hBitmap, hPal};
assert(::Gdiplus::Status::Ok == bmp.GetLastStatus());
//  Do something...
}
//  Delete palette and bitmap after GDI+ bitmap object went out of scope.
if(FALSE == ::DeleteObject(hPal))
{
assert(false);
}
if(FALSE == ::DeleteObject(hBitmap))
{
assert(false);
}

你可以传递 NULL。下面的示例代码。

int main()
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
GUID encoder = {};
GetGdiplusEncoderClsid(L"image/png", &encoder); // https://stackoverflow.com/a/5346026/104458
HDC hdc = GetDC(NULL);
HBITMAP hBitmap = CreateCompatibleBitmap(hdc, 200, 200);
Bitmap bmp(hBitmap, NULL);
bmp.Save(L"File.png", &encoder);
return 0;
}