Search code examples
c++bitmappnggdi+image-conversion

C++ gdi::Bitmap to PNG Image in memory


I'm trying to send a screenshot of a window over tcp to a server. Getting the screenshot is no problem (using GDIplus). The networking is also easy for me. The problem is trying to convert the gdi+ Bitmap to a png (in memory) to get the data out of it and send it to the server. Can anyone help me please?


Solution

  • Gdiplus can save to file, or save to memory using IStream. See Gdiplus::Image::Save method

    //get gdi+ bitmap
    Gdiplus::Bitmap bitmap(hbitmap, nullptr);
    
    //write to IStream
    IStream* istream = nullptr;
    HRESULT hr = CreateStreamOnHGlobal(NULL, TRUE, &istream);
    CLSID clsid_png;
    CLSIDFromString(L"{557cf406-1a04-11d3-9a73-0000f81ef32e}", &clsid_png);
    bitmap.Save(istream, &clsid_png);
    

    The memory size is small enough that you can copy from IStream to a single buffer (see "Minimum example" for more detail)

    //get memory handle associated with istream
    HGLOBAL hg = NULL;
    GetHGlobalFromStream(istream, &hg);
    
    //copy IStream to buffer
    int bufsize = GlobalSize(hg);
    char *buffer = new char[bufsize];
    
    //lock & unlock memory
    LPVOID ptr = GlobalLock(hg);
    memcpy(buffer, ptr, bufsize);
    GlobalUnlock(hg);
    
    //release will automatically free the memory allocated in CreateStreamOnHGlobal 
    istream->Release();
    

    PNG is now available in buffer, its size is bufsize. You can work directly with the binary data, or convert to Base64 to send over the network.

    Minimum example:

    #include <iostream>
    #include <fstream>
    #include <vector>
    #include <Windows.h>
    #include <gdiplus.h>
    
    bool save_png_memory(HBITMAP hbitmap, std::vector<BYTE>& data)
    {
        Gdiplus::Bitmap bmp(hbitmap, nullptr);
    
        //write to IStream
        IStream* istream = nullptr;
        if (CreateStreamOnHGlobal(NULL, TRUE, &istream) != 0)
            return false;
    
        CLSID clsid_png;
        if (CLSIDFromString(L"{557cf406-1a04-11d3-9a73-0000f81ef32e}", &clsid_png)!=0)
            return false;
        Gdiplus::Status status = bmp.Save(istream, &clsid_png);
        if (status != Gdiplus::Status::Ok)
            return false;
    
        //get memory handle associated with istream
        HGLOBAL hg = NULL;
        if (GetHGlobalFromStream(istream, &hg) != S_OK)
            return 0;
    
        //copy IStream to buffer
        int bufsize = GlobalSize(hg);
        data.resize(bufsize);
    
        //lock & unlock memory
        LPVOID pimage = GlobalLock(hg);
        if (!pimage)
            return false;
        memcpy(&data[0], pimage, bufsize);
        GlobalUnlock(hg);
    
        istream->Release();
        return true;
    }
    
    int main()
    {
        CoInitialize(NULL);
    
        ULONG_PTR token;
        Gdiplus::GdiplusStartupInput tmp;
        Gdiplus::GdiplusStartup(&token, &tmp, NULL);
    
        //take screenshot
        RECT rc;
        GetClientRect(GetDesktopWindow(), &rc);
        auto hdc = GetDC(0);
        auto memdc = CreateCompatibleDC(hdc);
        auto hbitmap = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
        auto oldbmp = SelectObject(memdc, hbitmap);
        BitBlt(memdc, 0, 0, rc.right, rc.bottom, hdc, 0, 0, SRCCOPY);
        SelectObject(memdc, oldbmp);
        DeleteDC(memdc);
        ReleaseDC(0, hdc);
    
        //save as png
        std::vector<BYTE> data;
        if(save_png_memory(hbitmap, data))
        {
            //write from memory to file for testing:
            std::ofstream fout("test.png", std::ios::binary);
            fout.write((char*)data.data(), data.size());
        }
        DeleteObject(hbitmap);
    
        Gdiplus::GdiplusShutdown(token);
        CoUninitialize();
    
        return 0;
    }