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

泪湿孤枕 提交于 2020-01-06 05:24:06

问题


I want to use GDI+ method Image::Save() to save a DDB to a file in the following scenario:

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", ...) ;

The problem is that Bitmap constructor asks for an HPALETTE when the bitmap is not a device-independent bitmap.

Where do I get the necessary HPALETTE from?


FOLLOWUP:
One of the answers suggests passing NULL as the HPALETTE parameter.
Here is a working example that does so. The result is a purely black and white image where all colors are lost.

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

回答1:


First (and this is unrelated to your main question):

When creating a bitmap for screen shot, don't use a memory dc because that creates a monochrome bitmap. That's the main reason you are getting a black and white image (on my computer I just get a black image).

Don't use GetDC(0) inside another function. Every call to GetDC match have a matching ReleaseDC to avoid resource leak.

After calling BitBlt it is good practice to select hbitmap out of dc because you are basically finished drawing on dc.

The following code will work on 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);

Back to your question regarding the documentation:

Type: HPALETTE
Handle to a GDI palette used to define the bitmap colors if hbm is not a device-independent bitmap (DIB).

In addition,

Do not pass to the Bitmap::FromHBITMAP method a GDI bitmap or a GDI palette that is currently (or was previously) selected into a device context.

The code I posted obeys only one rule, that GDI bitmap is not currently selected in to a device context (but it was previously selected).

The documentation may apply to older versions of Windows. As far as I can see MFC's CImage class does not follow all these rules. New computer displays are all 24 or 32 bit, I don't know how you would get a palette for it.

To follow the documentation to the letter, you can convert DDB to DIB section, using CreateDIBSection and GetDIBits. Use the new DIB section hbitmap_dib in Bitmap::FromHBITMAP. This will satisfy all of the conditions: hbitmap is dib, it is not (and was not) selected in to a device context.

Or, Gdiplus::Bitmap has another method Bitmap::FromBITMAPINFO. If there is no palette, you can use this code instead:

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



回答2:


As CreateCompatibleBitmap remarks sate if you are dealing with color bitmaps we can also assume that hDC is a nonmemory device context (because memory device context will only create monochrome bitmaps) and the color palette used by this bitmap is the same color palette used by this device context. You can query it using GetCurrentObject method. However remarks to Bitmap.Bitmap(HBITMAP, HPALETTE) constructor state:

Do not pass to the GDI+ Bitmap::Bitmap constructor a GDI bitmap or a GDI palette that is currently (or was previously) selected into a device context.

So you can not used current device context palette directly and need to create a copy of it instead.

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



回答3:


You can pass NULL. Sample code below.

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


来源:https://stackoverflow.com/questions/45319996/how-to-construct-a-gdi-bitmap-object-from-a-device-dependent-hbitmap

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!