如何从依赖于设备的HBITMAP

时间:2017-07-26 07:16:57

标签: c++ winapi gdi+ gdi color-palette

我希望在以下场景中使用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

我从哪里获得必要的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) ;
}

3 个答案:

答案 0 :(得分:1)

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

为屏幕截图创建位图时,请勿使用内存直流,因为这会创建单色位图。这是你获得黑白图像的主要原因(在我的电脑上我得到的是黑色图像)。

请勿在其他功能中使用GetDC(0)。每次调用GetDC匹配都会匹配ReleaseDC以避免资源泄漏。

在致电BitBlt之后,最好从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);

返回有关文档的问题:

  

类型:HPALETTE
  如果hbm不是与设备无关的位图(DIB),则处理用于定义位图颜色的GDI调色板。

此外,

  

不要将GDI位图或当前(或以前)选择的GDI调色板传递给Bitmap :: FromHBITMAP方法。

我发布的代码只服从一条规则,GDI位图当前未被选入设备上下文(但之前已被选中)。

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

要按照文档的说明,您可以使用CreateDIBSectionGetDIBits将DDB转换为DIB部分。使用hbitmap_dib中的新DIB部分Bitmap::FromHBITMAP。这将满足所有条件: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);

答案 1 :(得分:0)

CreateCompatibleBitmap所述,如果你正在处理颜色位图,我们也可以假设hDC是一个非内存设备上下文(因为内存设备上下文只会创建单色位图)和使用的颜色调色板此位图与此设备上下文使用的颜色调色板相同。您可以使用GetCurrentObject方法进行查询。但Bitmap.Bitmap(HBITMAP, HPALETTE) constructor州的评论是:

  

不要将当前(或以前)选择的GDI位图或GDI调色板传递给GDI + Bitmap :: Bitmap构造函数。

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

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

答案 2 :(得分:-2)

您可以传递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;
}