winapipngbitblt

can I convert bmp in-memory data into a much more smaller png in-momory data?


my program 's purpose is making a screen capture every 1/16 second , and send it by socket to remote server .

current this program can work with BMP format screen capture , however , the BMP format data have too many bytes to send , it obviously slow the send and recv process .

my idea is : if I can convert the BMP into PNG , and zip it before send , maybe the program can work more smooth .

here is my code , select from gh0st project

LPVOID  m_lpvFullBits = NULL;
HDC m_hFullDC, m_hFullMemDC;
LPBITMAPINFO  m_lpbmi_full;
m_hFullDC = GetDC(NULL);
int m_nFullWidth    = ::GetSystemMetrics(SM_CXSCREEN);
int m_nFullHeight   = ::GetSystemMetrics(SM_CYSCREEN);
m_hFullMemDC    = ::CreateCompatibleDC(m_hFullDC);

m_lpbmi_full = (BITMAPINFO *) new BYTE[40];
BITMAPINFOHEADER    *lpbmih = &(m_lpbmi_full ->bmiHeader);
lpbmih->biSize = sizeof(BITMAPINFOHEADER);
lpbmih->biWidth = m_nFullWidth  ;
lpbmih->biHeight = m_nFullHeight    ;
lpbmih->biPlanes = 1;
lpbmih->biBitCount = 32;       // 32 bit per pixel
lpbmih->biCompression = BI_RGB;
lpbmih->biXPelsPerMeter = 0;
lpbmih->biYPelsPerMeter = 0;
lpbmih->biClrUsed = 0;
lpbmih->biClrImportant = 0;
lpbmih->biSizeImage = (((lpbmih->biWidth * lpbmih->biBitCount + 31) & ~31) >> 3) * lpbmih->biHeight;

HBITMAP m_hFullBitmap   = ::CreateDIBSection(m_hFullDC, m_lpbmi_full, DIB_RGB_COLORS, &m_lpvFullBits, NULL, NULL);

::SelectObject(m_hFullMemDC, m_hFullBitmap);

::BitBlt(m_hFullMemDC, 0, 0, m_nFullWidth, m_nFullHeight, m_hFullDC, 0, 0, SRCCOPY);

until this statement :

::BitBlt(m_hFullMemDC, 0, 0, m_nFullWidth, m_nFullHeight, m_hFullDC, 0, 0, SRCCOPY);

BMP pixel data is saved at address begin from m_lpvFullBits , right ?

Now what I want to know is , can I use the current known info such as width , height , BMP pixel data .. , rebuild a new png format in-memory data which is much more smaller than current BMP data , and send the new png format data by socket to remote server ?

thanks for any help


Solution

  • Yes, you can. In my opinion GDI+ is good enough for this task. First, create a Gdiplus::Bitmap from HBITMAP, then you save it to a IStream. From the IStream you can get the byte count via IStream::Stat(). Finally, read it into a byte array with IStream::Read(). Now you have your PNG in memory.

    Here is a minimal example that you can copy and paste to a CPP file and compile. Note that error-handling and cleanup code is omitted.

    #include <Windows.h>
    #include <gdiplus.h>
    #include <iostream>
    using namespace Gdiplus;
    
    #pragma comment(lib, "gdiplus.lib") // or you specify it in linker option
    
    IStream * PngMemStreamFrom(HBITMAP hbm)
    {
        // image/png  : {557cf406-1a04-11d3-9a73-0000f81ef32e}
        const CLSID clsidPngEncoder =
            { 0x557cf406, 0x1a04, 0x11d3,
            { 0x9a,0x73,0x00,0x00,0xf8,0x1e,0xf3,0x2e } };
        IStream *stream = NULL;
        Bitmap *bmp = Bitmap::FromHBITMAP(hbm, NULL);
        CreateStreamOnHGlobal(NULL, TRUE, &stream);
        bmp->Save(stream, &clsidPngEncoder);    
        delete bmp;
        return stream;
    }
    
    void ScreenshotTest(LPCWSTR szPath)
    {
        LPVOID  m_lpvFullBits = NULL;
        HDC m_hFullDC, m_hFullMemDC;
        LPBITMAPINFO  m_lpbmi_full;
        m_hFullDC = GetDC(NULL);
        int m_nFullWidth    = ::GetSystemMetrics(SM_CXSCREEN);
        int m_nFullHeight   = ::GetSystemMetrics(SM_CYSCREEN);
        m_hFullMemDC    = ::CreateCompatibleDC(m_hFullDC);
    
        m_lpbmi_full = (BITMAPINFO *) new BYTE[40];
        BITMAPINFOHEADER    *lpbmih = &(m_lpbmi_full ->bmiHeader);
        lpbmih->biSize = sizeof(BITMAPINFOHEADER);
        lpbmih->biWidth = m_nFullWidth  ;
        lpbmih->biHeight = m_nFullHeight    ;
        lpbmih->biPlanes = 1;
        lpbmih->biBitCount = 32;       // 32 bit per pixel
        lpbmih->biCompression = BI_RGB;
        lpbmih->biXPelsPerMeter = 0;
        lpbmih->biYPelsPerMeter = 0;
        lpbmih->biClrUsed = 0;
        lpbmih->biClrImportant = 0;
        lpbmih->biSizeImage = (((lpbmih->biWidth * lpbmih->biBitCount + 31) & ~31) >> 3) * lpbmih->biHeight;
    
        HBITMAP m_hFullBitmap = ::CreateDIBSection(m_hFullDC, m_lpbmi_full, DIB_RGB_COLORS, &m_lpvFullBits, NULL, NULL);
        ::SelectObject(m_hFullMemDC, m_hFullBitmap);
        ::BitBlt(m_hFullMemDC, 0, 0, m_nFullWidth, m_nFullHeight, m_hFullDC, 0, 0, SRCCOPY);
    
        IStream *stream = PngMemStreamFrom(m_hFullBitmap);
    
        STATSTG stat = {0};
        stream->Stat(&stat, STATFLAG_NONAME);
        UINT64 cbSize = stat.cbSize.QuadPart;
        std::cout << "mem stream byte count = " << cbSize << "\n";
        LPBYTE buffer = new BYTE[cbSize];
        // IMPORTANT! must seek to offset zero before read it
        LARGE_INTEGER offZero = {0};
        stream->Seek(offZero, STREAM_SEEK_SET, NULL);
        stream->Read(buffer, cbSize, NULL);
        // do something with buffer, such as save to disk
        HANDLE hfile = CreateFile(szPath, GENERIC_WRITE, 0, NULL,
            CREATE_ALWAYS, 0, NULL);
        DWORD cbWritten = 0;
        WriteFile(hfile, buffer, cbSize, &cbWritten, NULL);
        CloseHandle(hfile);
        // TODO: release m_hFullDC, m_hFullMemDC... here
    }
    
    int main()
    {
        ULONG_PTR token = NULL;
        GdiplusStartupInput gdipIn;
        GdiplusStartupOutput gdipOut;
        GdiplusStartup(&token, &gdipIn, &gdipOut);
        ScreenshotTest(L"D:\\Test.png");
        GdiplusShutdown(token);
        return 0;
    }