位圖文件讀寫綜述
作者:
吉林大學
胡卓瑋
一、位圖文件結構
-
位圖文件頭
-
位圖信息
2.1
位圖信息頭
2.2
顏色表
-
位圖數據
二、位圖文件讀寫操作
-
類的聲明
-
位圖的讀取
-
位圖讀取過程中的調色板的創建和調用
-
位圖的顯示
-
位圖的存儲
-
新位圖的創建
-
其它問題
三、
CFG_DIB
的使用
下載本文配套代碼
關于位圖文件操作的資料很多。為了方便開發人員的工作,寫下本文,介紹了位圖文件結構,在此基礎之上設計了通用類
CFG_DIB
,用于進行位圖文件的讀寫操作。
一、位圖文件結構
位圖文件由三部分組成:文件頭
+
位圖信息
+
位圖像素數據
1
、位圖文件頭
。位圖文件頭主要用于識別位圖文件。以下是位圖文件頭結構的定義:
typedef struct tagBITMAPFILEHEADER { // bmfh
??? WORD??? bfType;
??? DWORD?? bfSize;
??? WORD??? bfReserved1;
??? WORD??? bfReserved2;
??? DWORD?? bfOffBits;
} BITMAPFILEHEADER;
其中的
bfType
值應該是
“BM”
(
0x4d42
),標志該文件是位圖文件。
bfSize
的值是位圖文件的大小。
2
、位圖信息
中所記錄的值用于分配內存,設置調色板信息,讀取像素值等。
以下是位圖信息結構的定義:
typedef struct tagBITMAPINFO {
??? BITMAPINFOHEADER??? bmiHeader;
??? RGBQUAD????????? ???bmiColors[1];
} BITMAPINFO;
可見位圖信息也是由兩部分組成的:位圖信息頭
+
顏色表
2.1
位圖信息頭。
位圖信息頭包含了單個像素所用字節數以及描述顏色的格式,此外還包括位圖的寬度、高度、目標設備的位平面數、圖像的壓縮格式。以下是位圖信息頭結構的定義:
typedef struct tagBITMAPINFOHEADER{ // bmih
??? DWORD? biSize;
??? LONG?? biWidth;
??? LONG?? biHeight;
?
??WORD?? biPlanes;
??? WORD?? biBitCount
??? DWORD? biCompression;
??? DWORD? biSizeImage;
??? LONG?? biXPelsPerMeter;
??? LONG?? biYPelsPerMeter;
??? DWORD? biClrUsed;
??? DWORD? biClrImportant;
} BITMAPINFOHEADER;
下表是對結構體當中各個成員的說明:
結構成員
|
說
明
|
biSize
|
結構
BITMAPINFOHEADER
的字節數,即
sizeof(BITMAPINFOHEADER)*
|
biWidth
|
以像素為單位的圖像寬度
*
|
biHeight
|
以像素為單位的圖像長度
*
|
biplanes
|
目標設備的位平面數
|
biBitCount
|
每個像素的位數
*
(
1
)
|
biCompression
|
圖像的壓縮格式(這個值幾乎總是為
0
)
|
biSizeImage
|
以字節為單位的圖像數據的大小(對
BI_RGB
壓縮方式而言)
|
biXPelsPermeter
|
水平方向上的每米的像素個數
|
biYpelsPerMeter
|
垂直方向上的每米的像素個數
|
biClrused
|
調色板中實際使用的顏色數(
2
)
|
biClrImportant
|
顯示位圖時必須的顏色數(
3
)
|
說明:
*
是需要加以注意的部分,因為它們是我們在進行位圖操作時經常參考的變量
(
1
)對于每個像素的字節數,分別有一下意義:
0
,用在
JPEG
格式中
1
,單色圖,調色板中含有兩種顏色,也就是我們通常說的黑白圖片
4
,
16
色圖
8
,
256
色圖,通常說的灰度圖
16
,
64K
圖,一般沒有調色板,圖像數據中每兩個字節表示一個像素,
5
個或
6
個位表示一個
RGB
分量
24
,
16M
真彩色圖,一般沒有調色板,圖像數據中每
3
個字節表示一個像素,每個字節表示一個
RGB
分量
32
,
4G
真彩色,一般沒有調色板,每
4
個字節表示一個像素,相對
24
位真彩圖而言,加入了一個透明度,即
RGBA
模式
(
2
)這個值通常為
0
,表示使用
biBitCount
確定的全部顏色,例外是使用的顏色數目小于指定的顏色深度的顏色數目的最大值。
(
3
)這個值通常為
0
,表示所有的顏色都是必需的
2.2
顏色表。
顏色表一般是針對
16
位以下的圖像而設置的,對于
16
位和
16
位以上的圖像,由于其位圖像素數據中直接對對應像素的
RGB(A)
顏色進行描述,因而省卻了調色板。而對于
16
位以下的圖像,由于其位圖像素數據中記錄的只是調色板索引值,因而需要根據這個索引到調色板去取得相應的
RGB(A)
顏色。顏色表的作用就是創建調色板。
下圖是帶調色板和不帶調色板的位圖的簡單示意圖
圖
1
帶調色板和不帶調色板位圖之間的區別
顏色表是由顏色表項組成的,顏色表項結構的定義如下:
typedef struct tagRGBQUAD { // rgbq
??? BYTE??? rgbBlue;
??? BYTE??? rgbGreen;
??? BYTE??? rgbRed;
??? BYTE??? rgbReserved;
} RGBQUAD;
其中需要注意的問題是,
RGBQUAD
結構中的顏色順序是
BGR
,而不是平常的
RGB
。
3
、位圖數據。
最后,在位圖文件頭、位圖信息頭、位圖顏色表之后,便是位圖的主體部分:位圖數據。根據不同的位圖,位圖數據所占據的字節數也是不同的,比如,對于
8
位位圖,每個字節代表了一個像素,對于
16
位位圖,每兩個字節代表了一個像素,對于
24
位位圖,每三個字節代表了一個像素,對于
32
位位圖,每四個字節代表了一個像素。
二、位圖文件讀寫操作
認識了位圖文件的結構以后,對特定位圖文件進行讀寫操作就顯得簡單了。本文附帶的源代碼中包含了一個能夠方便進行位圖讀寫操作的
C++
類。以下給出該類的使用參考,對于實現代碼中的關鍵部分做出了講解。
1
、類的聲明
class CFG_DIB : public CObject
{
public:
??????????? //
默認構造函數
??????????? CFG_DIB();
??????????? //
構造函數
,
根據圖象寬和高
,
以及記錄每個象素所需字節數來初始化
??????????? CFG_DIB(int width, int height, int nBitCounts);
??????????? virtual ~CFG_DIB();
?
public:
??????????? HBITMAP m_hBitmap;
??????????? LPBYTE m_lpDIBits; //DIB
位的起始位置
??????????? LPBITMAPINFOHEADER m_lpBMPHdr;???????????????????? //BITMAPINFOHEADER
信息
??????????? LPVOID m_lpvColorTable; //
顏色表信息
??????????? HPALETTE m_hPalette; //
調色板
?
private:
??????????? DWORD m_dwImageSize; //
非
BITMAPINFOHEADER
或
BITMAPFILEHEADER
的位
??????????? int m_nColorEntries; //
顏色表項的個數
?
//
顯示參數
public:
??????????? CPoint m_Dest; //
目的矩形域的左上角坐標
??????????? CSize m_DestSize; //
顯示矩形的寬度和高度
??????????? CPoint m_Src; //
原矩形左下角坐標
??????????? CSize m_SrcSize; //
原矩形寬度和高度
?
public:
??????????? void InitDestroy();//
初始化變量
??????????? void ComputePaletteSize(int nBitCounts); //
計算調色板大小
??????????? void ComputeImage();//
計算圖象大小
?
??????????? //
從
BMP
文件中讀入
DIB
信息
??????????? BOOL ReadFile(CFile* pFile);
?
??????????? //
從
BMP
文件中讀入
DIB
信息
,
與
ReadFile
不同的是使用
CreateSection
創建位圖位
??????????? BOOL ReadSection(CFile* pFile, CDC* pDC = NULL);
?
??????????? //
將
DIB
寫入文件,保存成
BMP
圖片格式
??????????? BOOL WriteFile(CFile* pFile);
?
??????????? //
創建新的位圖文件,根據參數
width,height,nBitCounts
分配內存空間
??????????? BOOL NewFile(int width, int height, int nBitCounts);
??????????? //
關閉位圖文件
??????????? BOOL CloseFile();
?
??????????? //
顯示位圖
??????????? BOOL Display(CDC* pDC);
?
??????????? HBITMAP CreateBitmap(CDC* pDC);?????????????????????????????????????????????? //
用
DIB
創建
DDB
??????????? HBITMAP CreateSection(CDC* pDC = NULL);????????????????????? //
創建位圖位數據,即象素數據
??????????? //
如果
DIB
沒有顏色表,可以用邏輯調色板
??????????? BOOL SetLogPalette(CDC* pDC);
??????????? //
如果
DIB
有顏色表,可以創建系統調色板
??????????? BOOL SetWinPalette();
??????????? //
把
DIB
對象的邏輯調色板選進設備環境里,然后實現調色板
??????????? UINT UseLogPalette(CDC* pDC);
?
??????????? //
得到
BitmapInfoHeader
的大小,包含顏色表數據
??????????? int GetHeaderSize()
??????????? {
??????????????????????? return sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * m_nColorEntries;
??????????? }
??????????? //
得到圖像的高度
??????????? int GetHeight()
??????????? {
??????????????????????? if(m_lpBMPHdr == NULL) return 0;
??????????????????????? return m_lpBMPHdr->biHeight;
??????????? }
??????????? //
得到圖像的寬度
??????????? int GetWidth()
??????????? {
??????????????????????? if(m_lpBMPHdr == NULL) return 0;
??????????????????????? return m_lpBMPHdr->biWidth;
??????????? }
??????????? //
得到圖像的大小
??????????? int GetImageSize()
??????????? {
??????????????????????? return m_dwImageSize;
??????????? }
??????????? long GetLineBit();?????????????????????? //
得到一行的象素數
};
2
、位圖的讀取。
CFG_DIB
提供了兩個從位圖文件讀取位圖數據的方法:
ReadFile
和
ReadSection
,二者不同之處,前者使用動態分配內存的方法初始化存儲位位圖數據的指針,后者則使用
API
函數,根據位圖信息初始化存儲位圖數據的指針。
方法
1
m_lpDIBits = (LPBYTE) new char[m_dwImageSize];
方法
2
m_hBitmap = ::CreateDIBSection(pDC->GetSafeHdc(),
???????? (LPBITMAPINFO) m_lpBMPHdr, DIB_RGB_COLORS,
???????? (LPVOID*) &m_lpDIBits, NULL, 0);
3
、位圖讀取過程中的調色板的創建和調用。
關于調色板的詳細情況,本文不作詳細介紹,只是對讀取位圖的過程中需要調用的對調色板進行操作的相關函數進行說明。
讀取文件的過程中,計算出調色板大小,然后調用創建調色板函數:
ComputePaletteSize(m_lpBMPHdr->biBitCount);
SetWinPalette();
在顯示位圖之前,設置調色板:
if(m_hPalette != NULL) {
???? ::SelectPalette(pDC->GetSafeHdc(), m_hPalette, TRUE);
}
4
、位圖的顯示。
位圖的顯示還是調用
Windows
的
API
函數來進行,需要傳遞的參數包括當前位圖信息頭,位圖數據等:
::StretchDIBits(pDC->GetSafeHdc(), m_Dest.x, m_Dest.y,
????????????????
????????????m_DestSize.cx, m_DestSize.cy,
???????????????????????????? m_Src.x, m_Src.y,
???????????????????????????? m_SrcSize.cx, m_SrcSize.cy,
???????????????????????????? m_lpDIBits, (LPBITMAPINFO) m_lpBMPHdr,
???????????????????????????? DIB_RGB_COLORS, SRCCOPY);
其中的
m_Dest
,
m_DestSize
,
m_Src
,
m_SrcSize
分別代表了圖像在當前設備上顯示的左上角坐標和范圍以及需要顯示的源圖像的左下角坐標和范圍。此處需要說明的是,位圖數據的字節數組是從圖像的最下面一行開始逐行向上存儲的,所以用戶在選取源位圖的現實范圍的時候需要特別注意!
m_Dest
,
m_DestSize
,
m_Src
,
m_SrcSize
需要在現實之前設置好。
5
、位圖的存儲。
位圖的存儲用
WriteFile
實現。
6
、新位圖的創建。
新位圖的創建由
NewFile
實現。需要的參數是位圖的寬度、高度、以及位圖像素占用的位數。
7
、其它問題。
存取位圖數據的字節數組有個問題需要引起開發人員的注意:字節數組中每個掃描行的字節數必需是
4
的倍數,如果不足要用
0
補齊。
以下是處理的辦法:
DWORD dwBytes = ((DWORD) m_lpBMPHdr->biWidth * m_lpBMPHdr->biBitCount) / 32;
if(((DWORD) m_lpBMPHdr->biWidth * m_lpBMPHdr->biBitCount) % 32) {
???? dwBytes++;
}
dwBytes *= 4;
m_dwImageSize = dwBytes * m_lpBMPHdr->biHeight;
這段代碼按照要求算出了用于記錄圖像數據的字節數組的大小。
三、
CFG_DIB
的使用
以下是
CFG_DIB
的使用示例代碼。
#include "fg_dib.h"
?
CFG_DIB m_fgdib;
?
//new file
m_fgdib.NewFile(width, height, nbitnum);
?
//open file
CFile* pf;
pf = new CFile;
pf->Open(sFileName, CFile::modeRead);
m_fgdib.ReadFile(pf);
pf->Close();
delete pf;
?
//draw BMP
m_fgdib.m_Dest.x = 0;
m_fgdib.m_Dest.y = 0;
m_fgdib.m_DestSize.cx = m_fgdib.GetWidth();
m_fgdib.m_DestSize.cy = m_fgdib.GetHeight();
m_fgdib.m_Src.x = 0;
m_fgdib.m_Src.y = 0;
m_fgdib.m_SrcSize.cx = m_fgdib.GetWidth();
m_fgdib.m_SrcSize.cy = m_fgdib.GetHeight();
CDC* pDC = GetDC();
m_fgdib.Display(pDC);
?
//close BMP
m_fgdib.CloseFile();
如果您在閱讀文章和使用代碼過程中遇到的問題,請與作者聯系:
吉林省長春市西民主大街
6
號地球探測科學與技術學院
2001
級碩士研究生
(130026)
歡迎訪問作者的主頁
:Forevergis.6to23.com