雖然現在網上已經有很多位元影像讀取、儲存的文章,很多寫的都很詳細,提供的原始碼功能也很強大,但是我仍然要自己重寫一個位元影像載入程式。主要是因為這些大牛們的文章寫的太深奧了,代碼功能太強大了,以至於像我這樣的菜鳥讀不懂。所以,我要力求簡潔。省略掉一些細節,比方說調色盤。為了能夠方便容易操作,我的程式只支援24位以上的位元影像檔案載入。
首先,瞭解下位元影像檔案的結構。24位以上的位元影像檔案包含3個部分:位元影像檔案頭(BITMAPFILEHEADER)、位元影像資訊頭(BITMAPINFOHEADER)、位元影像資料。
下面是MSDN中兩個資訊頭的具體定義:
typedef struct tagBITMAPFILEHEADER { WORD bfType; //映像類型:必須是‘BM’(BM的16進位編碼為:0x4d42) DWORD bfSize; //從檔案開頭到資料的位移量。也就是結構BITMAPFILEHEADER和結構BITMAPINFOHEADER的大小 WORD bfReserved1; //保留值。必須為0 WORD bfReserved2; //保留值。必須為0 DWORD bfOffBits; //檔案大小。包含著兩個結構,以及資料的總大小。} BITMAPFILEHEADER, *PBITMAPFILEHEADER; typedef struct tagBITMAPINFOHEADER{ DWORD biSize; //當前結構體的大小。 LONG biWidth; //位元影像的寬度。單位是像素 LONG biHeight; //位元影像高度。單位是像素 WORD biPlanes; //位元影像平面個數。必須是1 WORD biBitCount //位元影像的位元,也就是位元影像深度。可以是1、4、8、16、24、32。16位以下的位元影像檔案含有調色盤資訊。 DWORD biCompression; //壓縮方式。位元影像沒有壓縮,賦予BI_RGB參數(也就是0)。 DWORD biSizeImage; //位元影像資料佔用的位元組數。可以設定為預設值0。 LONG biXPelsPerMeter; //指定目標裝置水平解析度,單位是每米的像素個數。可設定為0 LONG biYPelsPerMeter; //指定目標裝置垂直解析度,同上。 DWORD biClrUsed; //指定本映像實際用到的像素。可設定為0 DWORD biClrImportant; //指定本映像重要的顏色個數。可設定為0,表示所有顏色都重要。} BITMAPINFOHEADER, *PBITMAPINFOHEADER;
接下來就是位元影像資料了:
位元影像按行存貯,位元影像的寬度不等於位元影像的行位元組數。每個像素佔用 biBitCount/8 個位元組,且每行佔用的位元組數必須是4位元組的整數倍!例如:101*101*24的位元影像,行佔用位元組數為:101*3=303,而303%4!=0,所以行佔用位元組數需修改為304,此時位元影像資料的實際大小為304*101,上面的biSizeImage就可設定為304*101。
下面是儲存位元影像檔案的結構體填充例:
BITMAPFILEHEADER bmheader; memset(&bmheader,0,sizeof(bmheader)); bmheader.bfType=0x4d42; //映像格式。必須為'BM'格式。 bmheader.bfOffBits = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER); //從檔案開頭到資料的位移量 bmheader.bfSize = m_nWidthBytes*m_nHeight + bmheader.bfOffBits;//檔案大小 BITMAPINFOHEADER bmInfo; memset(&bmInfo,0,sizeof(bmInfo)); bmInfo.biSize = sizeof(bmInfo); bmInfo.biWidth = m_nWidth; bmInfo.biHeight = m_nHeight; bmInfo.biPlanes = 1; bmInfo.biBitCount = m_nDeep; bmInfo.biCompression = BI_RGB;
註:m_nWidthBytes 可這樣計算:int m_nWidthBytes = ((m_nWidth*m_nDeep/8 + 3)/4) * 4;
下面是一個用來建立DIB位元影像,獲得HBITMAP控制代碼一個API。
HBITMAP CreateDIBSection(
HDC hdc, // handle to DC
CONST BITMAPINFO *pbmi, // bitmap data
UINT iUsage, // data type indicator
VOID **ppvBits, // bit values
HANDLE hSection, // handle to file mapping object
DWORD dwOffset // offset to bitmap bit values
);
(這裡就不詳細介紹了,可以去百科裡看看,如:http://www.hudong.com/wiki/CreateDIBSection)
大致說下開啟檔案檔案的流程:
1.讀入檔案頭BITMAPFILEHEADER資料。
2.讀入位元影像資訊頭BITMAPINFOHEADER資料。
3.讀取位元影像資料。
4.使用CreateDIBSection建立Dib位元影像,將讀取到的資料拷貝給Dib位元影像。
然後就可以使用Dib位元影像控制代碼進行位元影像的操作了。
儲存檔案流程:
1.填寫檔案頭BITMAPFILEHEADER,並寫入檔案。
2.填寫位元影像資訊頭BITMAPINFOHEADER,並寫入檔案。
3.獲得位元影像資料(GetBitmapBits,或者是在CreateDIBSection時,將資料地址記錄下來),將資料寫入檔案。
功能(參考範例見文章末尾):
1.建立掩碼DC(CreateCompatibelDC),掩碼位元影像(CreateCompatibleBitmap),將位元影像選到DC中。
2.將 要的視窗的DC內容拷貝(BitBlt)給掩碼DC(也就是拷貝到了掩碼位元影像上了)。
3.接著就是上面說到的儲存檔案流程了。
附件:我將上面的操作封裝起來。
//DIBBitmap.h#pragma onceclass CDIBBitmap{public://方法 CDIBBitmap(void); ~CDIBBitmap(void); bool create(HDC hdc,int width,int height,int deep);//建立位元影像 bool create(HDC hDC,HBITMAP hBitmap);//通過拷貝來建立位元影像 bool load(HDC hDC,char *szFileName);//從檔案中價值位元影像 bool save(char *szFileName);//儲存位元影像到檔案 void clear(void);//清空資料 void sampleRender(HDC hDC,int x,int y);//簡單顯示public://屬性 //獲得位元影像 inline HBITMAP getBitmap(void){ return m_hDibBmp; } //獲得資料 inline BYTE * getBmpData(void){ return m_pBmpData; } //獲得寬度 inline int getWidth(void){ return m_nWidth; } //獲得高度 inline int getHeight(void){ return m_nHeight; } //獲得位元深度 inline int getDeep(void){ return m_nDeep; } //獲得一行位元組數 inline int getWidthBytes(void){ return m_nWidthBytes; } protected://成員 HBITMAP m_hDibBmp; BYTE *m_pBmpData;//指向位元影像資料 int m_nWidth; //寬度。(單位:像素pixel) int m_nHeight; //高度。pixel int m_nDeep; //深度。bit。深度必須大於24位 int m_nWidthBytes;//一行所需的位元組數。必須是4位元組的整數倍。};//DIBBitmap.cpp#include <Windows.h>#include <stdio.h>#include "DIBBitmap.h"//////////////////////////////////////////////////////////////////////////CDIBBitmap::CDIBBitmap(void){ m_hDibBmp = NULL; m_pBmpData = NULL; m_nWidth = 0; //寬度。(單位:像素pixel) m_nHeight = 0; //高度。pixel m_nDeep = 0; //深度。bit。深度必須大於24位 m_nWidthBytes = 0;}CDIBBitmap::~CDIBBitmap(void){ clear();}bool CDIBBitmap::create(HDC hdc,int width,int height,int deep){ clear(); m_nWidth = width; m_nHeight = height; m_nDeep = deep; BITMAPINFO bmInfo; memset(&bmInfo,0,sizeof(bmInfo)); bmInfo.bmiHeader.biSize = sizeof(bmInfo); bmInfo.bmiHeader.biWidth = m_nWidth; bmInfo.bmiHeader.biHeight = m_nHeight; bmInfo.bmiHeader.biPlanes = 1; bmInfo.bmiHeader.biBitCount = m_nDeep; bmInfo.bmiHeader.biCompression = BI_RGB; m_hDibBmp = CreateDIBSection(hdc,&bmInfo,DIB_RGB_COLORS,(void**)&m_pBmpData,NULL,0); if (m_hDibBmp == NULL) { return false; } m_nWidthBytes = ((m_nWidth*m_nDeep/8 + 3)/4) * 4; return true;}bool CDIBBitmap::create(HDC hDC,HBITMAP hBitmap){ if(NULL==hDC || NULL==hBitmap) { return false; } //獲得位元影像資訊 BITMAP bm; GetObject(hBitmap,sizeof(bm),&bm); //建立位元影像 if(!create(hDC,bm.bmWidth,bm.bmHeight,bm.bmBitsPixel)) { return false; } //獲得hBitmap資料,並拷貝給Dib位元影像 GetBitmapBits(hBitmap,bm.bmWidthBytes*bm.bmHeight,getBmpData()); return true;}void CDIBBitmap::clear(void){ if (m_hDibBmp != NULL) { DeleteObject(m_hDibBmp); } m_hDibBmp = NULL; m_pBmpData = NULL;}void CDIBBitmap::sampleRender(HDC hDC,int x,int y){ HDC memDC = CreateCompatibleDC(0); SelectObject(memDC,m_hDibBmp); BitBlt(hDC,x,y,m_nWidth,m_nHeight,memDC,0,0,SRCCOPY); DeleteDC(memDC);}bool CDIBBitmap::load(HDC hDC,char *szFileName){ FILE *fp = NULL; fopen_s(&fp,szFileName,"rb"); if (NULL == fp) { return false; } //讀入檔案頭 BITMAPFILEHEADER bmheader; if(fread(&bmheader,sizeof(bmheader),1,fp) !=1 ) { fclose(fp); return false; } //讀入位元影像資訊頭 BITMAPINFOHEADER bmInfo; if(fread(&bmInfo,sizeof(bmInfo),1,fp) !=1 ) { fclose(fp); return false; } /*註:目前程式不支援調色盤資料位元圖載入。 */ if (bmInfo.biBitCount < 16) { return false; } //建立DIB位元影像 if (!create(hDC,bmInfo.biWidth,bmInfo.biHeight,bmInfo.biBitCount)) { fclose(fp); return false; } //讀入位元影像資料 if(fread(m_pBmpData,m_nWidthBytes,m_nHeight,fp) != m_nHeight) { fclose(fp); return false; } fclose(fp); return true;}bool CDIBBitmap::save(char *szFileName){ if (NULL==m_hDibBmp || NULL==m_pBmpData) { return false; } FILE *fp = NULL; fopen_s(&fp,szFileName,"wb"); if (NULL == fp) { return false; } BITMAPFILEHEADER bmheader; memset(&bmheader,0,sizeof(bmheader)); bmheader.bfType=0x4d42; //映像格式。必須為'BM'格式。 bmheader.bfOffBits = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER); //從檔案開頭到資料的位移量 bmheader.bfSize = m_nWidthBytes*m_nHeight + bmheader.bfOffBits;//檔案大小 BITMAPINFOHEADER bmInfo; memset(&bmInfo,0,sizeof(bmInfo)); bmInfo.biSize = sizeof(bmInfo); bmInfo.biWidth = m_nWidth; bmInfo.biHeight = m_nHeight; bmInfo.biPlanes = 1; bmInfo.biBitCount = m_nDeep; bmInfo.biCompression = BI_RGB; if(fwrite(&bmheader,sizeof(bmheader),1,fp) != 1) { fclose(fp); return false; } if(fwrite(&bmInfo,sizeof(bmInfo),1,fp) != 1) { fclose(fp); return false; } for(int i=m_nHeight-1;i>=0;--i) { int index = m_nWidthBytes*i; if(fwrite(m_pBmpData+index,m_nWidthBytes,1,fp) != 1) { fclose(fp); return false; } } fclose(fp); return true;}//////////////////////////////////////////////////////////////////////////功能參考代碼:
//獲得裝置描述表m_hDeviceContext=GetDC(m_hWnd);//建立掩碼dcm_hBackDC=CreateCompatibleDC(m_hDeviceContext);//建立掩碼位元影像m_bBackBitmap=CreateCompatibleBitmap(m_hDeviceContext,m_nWidth,m_nHeight);SelectObject(m_hBackDC,m_bBackBitmap);...將m_hDeviceContext的內容使用BitBlt拷貝給m_hBackDC...CDIBBitmap bmp;if(!bmp.create(m_hBackDC,m_bBackBitmap)){g_log.write("失敗!");}else{CreateDirectory(TEXT("shot"),NULL);char buffer[64];SYSTEMTIME tm;GetLocalTime(&tm);sprintf_s(buffer,64,"shot/%d_%d_%d_%d_%d_%d.bmp",tm.wYear,tm.wMonth,tm.wDay,tm.wHour,tm.wMinute,tm.wSecond);if(!bmp.save(buffer)){g_log.writex("儲存位元影像【%s】失敗!",buffer);}}