2017-06-13 79 views
0

我的程序的目的是每1/16秒進行一次屏幕截圖,並通過套接字發送給遠程服務器。我可以將bmp內存數據轉換成更小的png內存數據嗎?

目前這個程序可以使用BMP格式的屏幕截圖,但是BMP格式的數據有太多的字節要發送,這顯然會拖慢send和recv過程。

我的想法是:如果我可以將BMP轉換成PNG,並在發送之前壓縮它,也許程序可以更順暢地工作。

這裏是我的代碼,從gh0st項目

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

選擇,直到這個說法:

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

BMP像素的數據被保存在地址從m_lpvFullBits開始,對不對?

現在我想知道的是,我可以使用當前已知的信息,如寬度,高度,BMP像素數據..,重建一個新的PNG格式的內存數據比目前的BMP數據小得多,並通過套接字發送新的PNG格式數據到遠程服務器?

感謝您的任何幫助

+1

是的,這是可能的。您需要使用[Windows圖像組件](https://msdn.microsoft.com/en-us/library/windows/desktop/ee719902.aspx)中的PNG編碼器。它在'IStream'接口上工作,所以你可以傳遞一個內存流實現。 – IInspectable

+3

_將BMP轉換爲PNG,並在發送之前對其進行壓縮...轉換爲PNG後不需要ZIP,因爲PNG格式已經「拉」了數據,並且它比ZIP更高效。 – zett42

+0

gdiplus會不會更容易使用來保存到PNG?或者它只能將PNG保存到磁盤而不是內存? –

回答

2

是的,你可以。在我看來,GDI +足夠完成這項任務。首先,從HBITMAP創建Gdiplus::Bitmap,然後將其保存到IStream。從IStream你可以通過IStream::Stat()得到字節數。最後,用IStream::Read()將它讀入一個字節數組。現在你的記憶中有你的PNG。

這是一個最小的例子,您可以複製並粘貼到CPP文件並進行編譯。 請注意,錯誤處理和清理代碼被省略。

#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; 
} 
+0

謝謝你的回覆,讓我試一下,之前比較大小 – yangl

+0

它可以工作,經過轉換,緩衝區的大小隻有原始BMP文件大小的1/10,順便說一句,我發送緩衝區到服務器後,如何在Windows上繪製它,仍然使用bitblt? – yangl

+0

@yangl'BitBlt'不能直接繪製PNG。如果你真的想'BitBlt',你應該把緩衝區寫入一個'IStream *',尋求零點,用'bmp = Gdiplus :: Bitmap :: FromStream(...)'創建流的位圖,然後' bmp-> GetHBITMAP()'和'BitBlt'爲'HBITMAP'。 – raymai97

相關問題