天天看點

将HDC儲存為BMP檔案

HDC在MSDN中的全稱為:The handle of device context。通常,我們都是用來做相應的顯示操作。

    熟悉WIN32的朋友對于其應該不會陌生,經常采用GetDC,GetWindowDC等等來擷取其句柄。而用得最多的,可能就是BeginPaint,如:

case WM_PAINT:  

    HDC hdc = BeginPaint(hWnd,&ps);  

    ...  

    EndPaint(hdc,&ps);  

    break;   

case WM_PAINT:      HDC hdc = BeginPaint(hWnd,&ps);     ...     EndPaint(hdc,&ps);      break;   

   使用起來非常簡單,但如果想将其内容儲存為普通的圖像文檔,可就沒那麼容易。确切地說,在隻知道HDC句柄的情況下,我們是無法儲存其内容的;但我們可以劍走偏鋒,将HDC的内容寫到一個緩存中,然後我們再儲存該緩存的内容即可。

   聽起來很簡單,卻又像很複雜,不是麼?沒關系,我們現在一步一步來。

   首先,我們需要一個HDC的句柄。如同前面所說,你可以有多種方法,比如GetDC,GetWindowDC,甚至是CreateDC。反正呢,你用什麼方法我不管,我隻要有一個HDC的句柄就好了。

   有了HDC的句柄,接下來我們所需要做的是,知道這HDC的大小,也就是寬度和長度。這個不難,我們隻要簡單地調用GetDeviceCaps,然後參數給予HORZRES或VERTRES即可:

int iWidth = GetDeviceCaps(hdc,HORZRES);  

int iHeight = GetDeviceCaps(hdc,VERTRES);  

int iWidth = GetDeviceCaps(hdc,HORZRES);    int iHeight = GetDeviceCaps(hdc,VERTRES);  

    為什麼要知道大小呢?因為我們要用它來建立緩存。而這緩存,說白了,其實就是一個BMP格式的資料結構而已。

    為了建立這個關鍵的緩存,我們必須調用CreateDIBSection函數,而該函數形參又用到BITMAPINFOHEADER,是以我們的一切,就先從填充該結構體開始。

    該結構體定義如下:

typedef struct tagBITMAPINFO  

{   

  BITMAPINFOHEADER bmiHeader;   

  RGBQUAD bmiColors[1];   

} BITMAPINFO;  

typedef struct tagBITMAPINFO    {       BITMAPINFOHEADER bmiHeader;       RGBQUAD bmiColors[1];     } BITMAPINFO;  

    結構體裡面還有一個BITMAPINFOHEADER,其定義如下:

typedef struct tagBITMAPINFOHEADER   

  DWORD biSize;   

  LONG biWidth;   

  LONG biHeight;   

  WORD biPlanes;   

  WORD biBitCount   

  DWORD biCompression;   

  DWORD biSizeImage;   

  LONG biXPelsPerMeter;   

  LONG biYPelsPerMeter;   

  DWORD biClrUsed;   

  DWORD biClrImportant;   

} BITMAPINFOHEADER;  

typedef struct tagBITMAPINFOHEADER     {       DWORD biSize;       LONG biWidth;       LONG biHeight;       WORD biPlanes;       WORD biBitCount       DWORD biCompression;       DWORD biSizeImage;       LONG biXPelsPerMeter;       LONG biYPelsPerMeter;       DWORD biClrUsed;       DWORD biClrImportant;     } BITMAPINFOHEADER;  

    這麼多變量,是不是有點頭暈?大可不必緊張,其實我們隻需要填充其中幾個,其它統統置為0即可:

BITMAPINFO bmpInfo = {0};  

bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);  

bmpInfo.bmiHeader.biWidth = iWidth;  

bmpInfo.bmiHeader.biHeight = iHeight;  

bmpInfo.bmiHeader.biPlanes = 1;  

bmpInfo.bmiHeader.biBitCount = 24;  

BITMAPINFO bmpInfo = {0};    bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);    bmpInfo.bmiHeader.biWidth = iWidth;    bmpInfo.bmiHeader.biHeight = iHeight;    bmpInfo.bmiHeader.biPlanes = 1;    bmpInfo.bmiHeader.biBitCount = 24;  

    一切從最簡單做起,對于BMP而言,最簡單的自然是24位位圖,這就是為什麼biPlanes和biBitCount分别設定為1和24的原因。

    填充完BITMAPINFO結構,我們還是不能馬上調用CreateDIBSection,因為形參中還有一個HDC。雖然我們可以直接采用已知的HDC句柄,但接下來還要将建立的HBITMAP和HDC相連接配接,是以我們還是先建立一個緩存DC:

HDC hdcMem = CreateCompatibleDC(hdc);  

    一切準備就緒之後,就調用CreateDIBSection吧:

BYTE *pData = NULL;  

hBmp = CreateDIBSection(hdcMem,&bmpInfo,DIB_RGB_COLORS,reinterpret_cast<VOID **>(&pData),NULL,0);  

BYTE *pData = NULL;    hBmp = CreateDIBSection(hdcMem,&bmpInfo,DIB_RGB_COLORS,reinterpret_cast<VOID **>(&pData),NULL,0);  

    pData是配置設定的一個記憶體空間,将來用來存儲HDC的内容,隻不過現在一切都是空的。如果你将這資料儲存出來,你會發現一團漆黑。

    将HBITMAP和HDC結合:

hOldObj = SelectObject(hdcMem, hBmp);  

    至此為止,我們前期工作已經準備就緒,我們隻需要将HDC的内容用BitBlt繪制到緩存中即可:

BitBlt(hdcMem,  

       0,  

       iWidth,  

       iHeight,  

       hdc,  

       SRCCOPY);  

BitBlt(hdcMem,           0,           0,           iWidth,           iHeight,           hdc,           0,           0,           SRCCOPY);  

  這裡其實還有一個小技巧,如果你是想繪制HDC的某個區域,你隻需要用StretchBlt替代即可:

StretchBlt(hdcMem,  

            0,  

            iWidth,  

            iHeight,  

            hdc,  

            rcDC.left,  

            rcDC.top,  

            rcDC.right - rcDC.left + 1,  

            rcDC.bottom - rcDC.top + 1,  

            SRCCOPY);  

StretchBlt(hdcMem,                0,                0,                iWidth,                iHeight,                hdc,                rcDC.left,                rcDC.top,                rcDC.right - rcDC.left + 1,                rcDC.bottom - rcDC.top + 1,                SRCCOPY);  

    喜歡追究問題的你,也許會發現,在調用該函數之後,pData所指向的記憶體緩沖區已經改變。是的,沒錯,這些改變的資料就是我們所需要的。接下來我們所需要做的僅僅是,将這資料按BMP檔案的格式,儲存下來即可。

    BMP檔案格式其實很簡單,最開始是檔案頭資訊,然後是圖檔資訊,接下來是資料。我們隻需要按照這格式,順序将資料寫入即可。

    檔案頭資訊和圖檔資訊,微軟已經為我們定義好了相應的結構體:

    BMP資訊:

    檔案頭資訊:

typedef struct tagBITMAPFILEHEADER   

{  

  WORD bfType;   

  DWORD bfSize;   

  WORD bfReserved1;   

  WORD bfReserved2;   

  DWORD bfOffBits;   

} BITMAPFILEHEADER;   

typedef struct tagBITMAPFILEHEADER     {      WORD bfType;       DWORD bfSize;       WORD bfReserved1;       WORD bfReserved2;       DWORD bfOffBits;     } BITMAPFILEHEADER;   

    我們首先填充這兩個結構體數值:

BITMAPINFOHEADER bmInfoHeader = {0};  

bmInfoHeader.biSize = sizeof(BITMAPINFOHEADER);  

bmInfoHeader.biWidth = iWidth;  

bmInfoHeader.biHeight = iHeight;  

bmInfoHeader.biPlanes = 1;  

bmInfoHeader.biBitCount = 24;  

//Bimap file header in order to write bmp file  

BITMAPFILEHEADER bmFileHeader = {0};  

bmFileHeader.bfType = 0x4d42;  //bmp    

bmFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);  

bmFileHeader.bfSize = bmFileHeader.bfOffBits + ((bmInfoHeader.biWidth * bmInfoHeader.biHeight) * 3); ///3=(24 / 8)  

BITMAPINFOHEADER bmInfoHeader = {0};    bmInfoHeader.biSize = sizeof(BITMAPINFOHEADER);    bmInfoHeader.biWidth = iWidth;    bmInfoHeader.biHeight = iHeight;    bmInfoHeader.biPlanes = 1;    bmInfoHeader.biBitCount = 24;        //Bimap file header in order to write bmp file    BITMAPFILEHEADER bmFileHeader = {0};    bmFileHeader.bfType = 0x4d42;  //bmp      bmFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);    bmFileHeader.bfSize = bmFileHeader.bfOffBits + ((bmInfoHeader.biWidth * bmInfoHeader.biHeight) * 3); ///3=(24 / 8)  

    接下來的事情,估計大家都輕車熟路了。建立檔案,然後寫入資料,儲存,完畢。

HANDLE hFile = CreateFile(strFile.c_str(),GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);  

DWORD dwWrite = 0;  

WriteFile(hFile,&bmFileHeader,sizeof(BITMAPFILEHEADER),&dwWrite,NULL);  

WriteFile(hFile,&bmInfoHeader, sizeof(BITMAPINFOHEADER),&dwWrite,NULL);  

WriteFile(hFile,&vtData[0], vtData.size(),&dwWrite,NULL);  

CloseHandle(hFile);  

HANDLE hFile = CreateFile(strFile.c_str(),GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);    DWORD dwWrite = 0;    WriteFile(hFile,&bmFileHeader,sizeof(BITMAPFILEHEADER),&dwWrite,NULL);    WriteFile(hFile,&bmInfoHeader, sizeof(BITMAPINFOHEADER),&dwWrite,NULL);    WriteFile(hFile,&vtData[0], vtData.size(),&dwWrite,NULL);    CloseHandle(hFile);  

    文章的最後,是參考源代碼:

#ifdef UNICODE  

    #ifndef TSTRING  

        #define TSTRING std::wstring  

    #endif  

#else  

        #define TSTRING std::string  

#endif  

BOOL WriteBmp(const TSTRING &strFile,const std::vector<BYTE> &vtData,const SIZE &sizeImg);  

BOOL WriteBmp(const TSTRING &strFile,HDC hdc);  

BOOL WriteBmp(const TSTRING &strFile,HDC hdc,const RECT &rcDC);  

BOOL WriteBmp(const TSTRING &strFile,const std::vector<BYTE> &vtData,const SIZE &sizeImg)   

{     

    BITMAPINFOHEADER bmInfoHeader = {0};  

    bmInfoHeader.biSize = sizeof(BITMAPINFOHEADER);  

    bmInfoHeader.biWidth = sizeImg.cx;  

    bmInfoHeader.biHeight = sizeImg.cy;  

    bmInfoHeader.biPlanes = 1;  

    bmInfoHeader.biBitCount = 24;  

    //Bimap file header in order to write bmp file  

    BITMAPFILEHEADER bmFileHeader = {0};  

    bmFileHeader.bfType = 0x4d42;  //bmp    

    bmFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);  

    bmFileHeader.bfSize = bmFileHeader.bfOffBits + ((bmInfoHeader.biWidth * bmInfoHeader.biHeight) * 3); ///3=(24 / 8)  

    HANDLE hFile = CreateFile(strFile.c_str(),GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);  

    if(hFile == INVALID_HANDLE_VALUE)  

    {  

        return FALSE;  

    }  

    DWORD dwWrite = 0;  

    WriteFile(hFile,&bmFileHeader,sizeof(BITMAPFILEHEADER),&dwWrite,NULL);  

    WriteFile(hFile,&bmInfoHeader, sizeof(BITMAPINFOHEADER),&dwWrite,NULL);  

    WriteFile(hFile,&vtData[0], vtData.size(),&dwWrite,NULL);  

    CloseHandle(hFile);  

    return TRUE;  

}   

BOOL WriteBmp(const TSTRING &strFile,HDC hdc)  

    int iWidth = GetDeviceCaps(hdc,HORZRES);  

    int iHeight = GetDeviceCaps(hdc,VERTRES);  

    RECT rcDC = {0,0,iWidth,iHeight};  

    return WriteBmp(strFile,hdc,rcDC);    

}  

BOOL WriteBmp(const TSTRING &strFile,HDC hdc,const RECT &rcDC)  

    BOOL bRes = FALSE;  

    BITMAPINFO bmpInfo = {0};  

    BYTE *pData = NULL;  

    SIZE sizeImg = {0};  

    HBITMAP hBmp = NULL;  

    std::vector<BYTE> vtData;  

    HGDIOBJ hOldObj = NULL;  

    HDC hdcMem = NULL;  

    //Initilaize the bitmap information   

    bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);  

    bmpInfo.bmiHeader.biWidth = rcDC.right - rcDC.left;  

    bmpInfo.bmiHeader.biHeight = rcDC.bottom - rcDC.top;  

    bmpInfo.bmiHeader.biPlanes = 1;  

    bmpInfo.bmiHeader.biBitCount = 24;  

    //Create the compatible DC to get the data  

    hdcMem = CreateCompatibleDC(hdc);  

    if(hdcMem == NULL)  

        goto EXIT;  

    //Get the data from the memory DC     

    hBmp = CreateDIBSection(hdcMem,&bmpInfo,DIB_RGB_COLORS,reinterpret_cast<VOID **>(&pData),NULL,0);  

    if(hBmp == NULL)  

    hOldObj = SelectObject(hdcMem, hBmp);  

    //Draw to the memory DC  

    sizeImg.cx = bmpInfo.bmiHeader.biWidth;  

    sizeImg.cy = bmpInfo.bmiHeader.biHeight;  

    StretchBlt(hdcMem,  

                0,  

                sizeImg.cx,  

                sizeImg.cy,  

                hdc,  

                rcDC.left,  

                rcDC.top,  

                rcDC.right - rcDC.left + 1,  

                rcDC.bottom - rcDC.top + 1,  

                SRCCOPY);  

    vtData.resize(sizeImg.cx * sizeImg.cy * 3);  

    memcpy(&vtData[0],pData,vtData.size());  

    bRes = WriteBmp(strFile,vtData,sizeImg);  

    SelectObject(hdcMem, hOldObj);  

EXIT:  

    if(hBmp != NULL)  

        DeleteObject(hBmp);  

    if(hdcMem != NULL)  

        DeleteDC(hdcMem);  

    return bRes;  

#ifdef UNICODE#ifndef TSTRING#define TSTRING std::wstring#endif#else#ifndef TSTRING#define TSTRING std::string#endif#endifBOOL WriteBmp(const TSTRING &strFile,const std::vector<BYTE> &vtData,const SIZE &sizeImg);BOOL WriteBmp(const TSTRING &strFile,HDC hdc);BOOL WriteBmp(const TSTRING &strFile,HDC hdc,const RECT &rcDC);BOOL WriteBmp(const TSTRING &strFile,const std::vector<BYTE> &vtData,const SIZE &sizeImg) { BITMAPINFOHEADER bmInfoHeader = {0};bmInfoHeader.biSize = sizeof(BITMAPINFOHEADER);bmInfoHeader.biWidth = sizeImg.cx;bmInfoHeader.biHeight = sizeImg.cy;bmInfoHeader.biPlanes = 1;bmInfoHeader.biBitCount = 24;//Bimap file header in order to write bmp fileBITMAPFILEHEADER bmFileHeader = {0};bmFileHeader.bfType = 0x4d42;  //bmp  bmFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);bmFileHeader.bfSize = bmFileHeader.bfOffBits + ((bmInfoHeader.biWidth * bmInfoHeader.biHeight) * 3); ///3=(24 / 8)HANDLE hFile = CreateFile(strFile.c_str(),GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);if(hFile == INVALID_HANDLE_VALUE){return FALSE;}DWORD dwWrite = 0;WriteFile(hFile,&bmFileHeader,sizeof(BITMAPFILEHEADER),&dwWrite,NULL);WriteFile(hFile,&bmInfoHeader, sizeof(BITMAPINFOHEADER),&dwWrite,NULL);WriteFile(hFile,&vtData[0], vtData.size(),&dwWrite,NULL);CloseHandle(hFile);return TRUE;} BOOL WriteBmp(const TSTRING &strFile,HDC hdc){int iWidth = GetDeviceCaps(hdc,HORZRES);int iHeight = GetDeviceCaps(hdc,VERTRES);RECT rcDC = {0,0,iWidth,iHeight};return WriteBmp(strFile,hdc,rcDC);}BOOL WriteBmp(const TSTRING &strFile,HDC hdc,const RECT &rcDC){BOOL bRes = FALSE;BITMAPINFO bmpInfo = {0};BYTE *pData = NULL;SIZE sizeImg = {0};HBITMAP hBmp = NULL;std::vector<BYTE> vtData;HGDIOBJ hOldObj = NULL;HDC hdcMem = NULL;//Initilaize the bitmap informationbmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);bmpInfo.bmiHeader.biWidth = rcDC.right - rcDC.left;bmpInfo.bmiHeader.biHeight = rcDC.bottom - rcDC.top;bmpInfo.bmiHeader.biPlanes = 1;bmpInfo.bmiHeader.biBitCount = 24;//Create the compatible DC to get the datahdcMem = CreateCompatibleDC(hdc);if(hdcMem == NULL){goto EXIT;}//Get the data from the memory DChBmp = CreateDIBSection(hdcMem,&bmpInfo,DIB_RGB_COLORS,reinterpret_cast<VOID **>(&pData),NULL,0);if(hBmp == NULL){goto EXIT;}hOldObj = SelectObject(hdcMem, hBmp);//Draw to the memory DCsizeImg.cx = bmpInfo.bmiHeader.biWidth;sizeImg.cy = bmpInfo.bmiHeader.biHeight;StretchBlt(hdcMem,0,0,sizeImg.cx,sizeImg.cy,hdc,rcDC.left,rcDC.top,rcDC.right - rcDC.left + 1,rcDC.bottom - rcDC.top + 1,SRCCOPY);vtData.resize(sizeImg.cx * sizeImg.cy * 3);memcpy(&vtData[0],pData,vtData.size());bRes = WriteBmp(strFile,vtData,sizeImg);SelectObject(hdcMem, hOldObj);EXIT:if(hBmp != NULL){DeleteObject(hBmp);}if(hdcMem != NULL){DeleteDC(hdcMem);}return bRes;}  

    一共有三個WriteBmp函數,使用上非常簡單。

    比如,我想儲存一個HDC,隻需要簡單地調用:

HDC hdc = GetDC(NULL);  

WriteBmp(TEXT("//NAND//DCSave.bmp"));  

ReleaseDC(NULL,hdc);  

HDC hdc = GetDC(NULL);    WriteBmp(TEXT("//NAND//DCSave.bmp"));    ReleaseDC(NULL,hdc);  

    如果想儲存HDC的某一個部分,同樣也很簡單:

RECT rcDC = {0,0,100,100};  

WriteBmp(TEXT("//NAND//DCSavePart.bmp"),rcDC);  

HDC hdc = GetDC(NULL);    RECT rcDC = {0,0,100,100};    WriteBmp(TEXT("//NAND//DCSavePart.bmp"),rcDC);    ReleaseDC(NULL,hdc);  

    這個函數還能做到一個很有意思的功能,就是截取螢幕。對于螢幕來說,也是一個HDC,我們隻要擷取螢幕的HDC句柄,剩下的就沒有什麼難度了:

HDC hdc = CreateDC(_T("DISPLAY"), NULL, NULL, NULL);  

WriteBmp(TEXT("//NAND//ScreenCapture.BMP"),hdc);  

DeleteDC(hdc);  

繼續閱讀