說到圖片,位圖(Bitmap)當(dāng)然是最簡(jiǎn)單的,它Windows顯示圖片的基本格式,其文件擴(kuò)展名為*.BMP。在Windows下,任何各式的圖片文件(包括視頻播放)都要轉(zhuǎn)化為位圖個(gè)時(shí)候才能顯示出來,各種格式的圖片文件也都是在位圖格式的基礎(chǔ)上采用不同的壓縮算法生成的(Flash中使用了適量圖,是按相同顏色區(qū)域存儲(chǔ)的)。
一、下面我們來看看位圖文件(*.BMP)的格式。
位圖文件主要分為如下3個(gè)部分:
塊名稱
|
對(duì)應(yīng)Windows結(jié)構(gòu)體定義
|
大小(Byte)
|
文件信息頭
|
BITMAPFILEHEADER
|
14
|
位圖信息頭
|
BITMAPINFOHEADER
|
40
|
RGB顏色陣列
|
BYTE*
|
由圖像長(zhǎng)寬尺寸決定
|
1、 文件信息頭BITMAPFILEHEADER
結(jié)構(gòu)體定義如下:
typedef struct tagBITMAPFILEHEADER { /* bmfh */
UINT bfType;
DWORD bfSize;
UINT bfReserved1;
UINT bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;
其中:
bfType
|
說明文件的類型,該值必需是0x4D42,也就是字符'BM'。
|
bfSize
|
說明該位圖文件的大小,用字節(jié)為單位
|
bfReserved1
|
保留,必須設(shè)置為0
|
bfReserved2
|
保留,必須設(shè)置為0
|
bfOffBits
|
說明從文件頭開始到實(shí)際的圖象數(shù)據(jù)之間的字節(jié)的偏移量。這個(gè)參數(shù)是非常有用的,因?yàn)槲粓D信息頭和調(diào)色板的長(zhǎng)度會(huì)根據(jù)不同情況而變化,所以你可以用這個(gè)偏移值迅速的從文件中讀取到位數(shù)據(jù)。
|
2、位圖信息頭BITMAPINFOHEADER
結(jié)構(gòu)體定義如下:
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結(jié)構(gòu)所需要的字?jǐn)?shù)。
|
biWidth
|
說明圖象的寬度,以象素為單位。
|
biHeight
|
說明圖象的高度,以象素為單位。注:這個(gè)值除了用于描述圖像的高度之外,它還有另一個(gè)用處,就是指明該圖像是倒向的位圖,還是正向的位圖。如果該值是一個(gè)正數(shù),說明圖像是倒向的,如果該值是一個(gè)負(fù)數(shù),則說明圖像是正向的。大多數(shù)的BMP文件都是倒向的位圖,也就是時(shí),高度值是一個(gè)正數(shù)。
|
biPlanes
|
為目標(biāo)設(shè)備說明位面數(shù),其值將總是被設(shè)為1。
|
biBitCount
|
說明比特?cái)?shù)/象素,其值為1、4、8、16、24、或32。但是由于我們平時(shí)用到的圖像絕大部分是24位和32位的,所以我們討論這兩類圖像。
|
biCompression
|
說明圖象數(shù)據(jù)壓縮的類型,同樣我們只討論沒有壓縮的類型:BI_RGB。
|
biSizeImage
|
說明圖象的大小,以字節(jié)為單位。當(dāng)用BI_RGB格式時(shí),可設(shè)置為0。
|
biXPelsPerMeter
|
說明水平分辨率,用象素/米表示。
|
biYPelsPerMeter
|
說明垂直分辨率,用象素/米表示。
|
biClrUsed
|
說明位圖實(shí)際使用的彩色表中的顏色索引數(shù)(設(shè)為0的話,則說明使用所有調(diào)色板項(xiàng))。
|
biClrImportant
|
說明對(duì)圖象顯示有重要影響的顏色索引的數(shù)目,如果是0,表示都重要。
|
3、RGB顏色陣列
有關(guān)RGB三色空間我想大家都很熟悉,這里我想說的是在Windows下,RGB顏色陣列存儲(chǔ)的格式其實(shí)BGR。也就是說,對(duì)于24位的RGB位圖像素?cái)?shù)據(jù)格式是:
對(duì)于32位的RGB位圖像素?cái)?shù)據(jù)格式是:
藍(lán)色B值
|
綠色G值
|
紅色R值
|
透明通道A值
|
透明通道也稱Alpha通道,該值是該像素點(diǎn)的透明屬性,取值在0(全透明)到255(不透明)之間。對(duì)于24位的圖像來說,因?yàn)闆]有Alpha通道,故整個(gè)圖像都不透明。
二、搞清了文件格式,下一步我們要實(shí)現(xiàn)加載。
加載文件的目的是要得到圖片屬性,以及RGB數(shù)據(jù),然后可以將其繪制在DC上(GDI),或是生成紋理對(duì)象(3D:OpenGL/Direct3D)。這兩種用途在數(shù)據(jù)處理上有點(diǎn)區(qū)別,我們主要按前一種用法講,在和3D有不同的地方,我們?cè)偬岢鰜怼?/span>
1、加載文件頭
//Load the file header
BITMAPFILEHEADER header;
memset(&header, 0, sizeof(header));
inf.read((char*)&header, sizeof(header));
if(header.bfType != 0x4D42)
return false;
這個(gè)很簡(jiǎn)單,沒有什么好說的。
2、加載位圖信息頭
//Load the image information header
BITMAPINFOHEADER infoheader;
memset(&infoheader, 0, sizeof(infoheader));
inf.read((char*)&infoheader, sizeof(infoheader));
m_iImageWidth = infoheader.biWidth;
m_iImageHeight = infoheader.biHeight;
m_iBitsPerPixel = infoheader.biBitCount;
這里我們得到了3各重要的圖形屬性:寬,高,以及每個(gè)像素顏色所占用的位數(shù)。
3、行對(duì)齊
由于Windows在進(jìn)行行掃描的時(shí)候最小的單位為4個(gè)字節(jié),所以當(dāng)
圖片寬 X 每個(gè)像素的字節(jié)數(shù) != 4的整數(shù)倍
時(shí)要在每行的后面補(bǔ)上缺少的字節(jié),以0填充(一般來說當(dāng)圖像寬度為2的冪時(shí)不需要對(duì)齊)。位圖文件里的數(shù)據(jù)在寫入的時(shí)候已經(jīng)進(jìn)行了行對(duì)齊,也就是說加載的時(shí)候不需要再做行對(duì)齊。但是這樣一來圖片數(shù)據(jù)的長(zhǎng)度就不是:寬 X 高 X 每個(gè)像素的字節(jié)數(shù) 了,我們需要通過下面的方法計(jì)算正確的數(shù)據(jù)長(zhǎng)度:
//Calculate the image data size
int iLineByteCnt = (((m_iImageWidth*m_iBitsPerPixel) + 31) >> 5) << 2;
m_iImageDataSize = iLineByteCnt * m_iImageHeight;
4、加載圖片數(shù)據(jù)
對(duì)于24位和32位的位圖文件,位圖數(shù)據(jù)的偏移量為sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER),也就是說現(xiàn)在我們可以直接讀取圖像數(shù)據(jù)了。
if(m_pImageData) delete []m_pImageData;
m_pImageData = new unsigned char[m_iImageDataSize];
inf.read((char*)m_pImageData, m_iImageDataSize);
如果你足夠細(xì)心,就會(huì)發(fā)現(xiàn)內(nèi)存m_pImageData里的數(shù)據(jù)的確是BGR格式,可以用個(gè)純藍(lán)色或者是純紅色的圖片測(cè)試一下。
5、繪制
好了,數(shù)據(jù)和屬性我們都有了,現(xiàn)在就可以拿來隨便用了,就和吃饅頭一樣,愛粘白糖粘白糖,愛粘紅糖粘紅糖。下面是我的GDI繪制代碼,僅作參考。
void CImage::DrawImage(HDC hdc, int iLeft, int iTop, int iWidth, int iHeight)
{
if(!hdc || m_pImageData == NULL)
return;
BITMAPINFO bmi;
memset(&bmi, 0, sizeof(bmi));
bmi.bmiHeader.biSize = sizeof(BITMAPINFO);
bmi.bmiHeader.biWidth = m_iImageWidth;
bmi.bmiHeader.biHeight = m_iImageHeight;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = m_iBitsPerPixel;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = m_iImageDataSize;
StretchDIBits(hdc, iLeft, iTop, iWidth, iHeight,
0, 0, m_iImageWidth, m_iImageHeight,
m_pImageData, &bmi, DIB_RGB_COLORS, SRCCOPY);
}
6、3D(OpenGL)的不同之處
如果你是想用剛才我們得到的數(shù)據(jù)生成紋理對(duì)象,那么你還要請(qǐng)出下面的問題。
首先,用來生成紋理的數(shù)據(jù)不需要對(duì)齊,也就是說不能在每行的后面加上對(duì)齊的字節(jié)。當(dāng)然在OpenGL里要求紋理圖片的尺寸為2的冪,所以這個(gè)問題實(shí)際上不存在;
其次,我們得到的圖形數(shù)據(jù)格式是BGR(BGRA),所以在生成紋理的時(shí)候,需指定格式為GL_BGR_EXT(GL_BGRA_EXT);否則需要做BGR->RGB(BGRA->RGBA)的轉(zhuǎn)化