圖示顯示之bmp資料提取
在Windows下,任何各式的圖片檔案(包括視訊播放)都要轉化為點陣圖個時候才能顯示出來,各種格式的圖片檔案也都是在點陣圖格式的基礎上採用不同的壓縮演算法生成的(Flash中使用了適量圖,是按相同顏色區域儲存的)。
一、下面我們來看看點陣圖檔案(*.BMP)的格式。
點陣圖檔案主要分為如下3個部分:
塊名稱 |
對應Windows結構體定義 |
大小(Byte) |
檔案資訊頭 |
BITMAPFILEHEADER |
14 |
點陣圖資訊頭 |
BITMAPINFOHEADER |
40 |
RGB顏色陣列 |
BYTE* |
由影象長寬尺寸決定 |
1、 檔案資訊頭BITMAPFILEHEADER
結構體定義如下:
typedef struct tagBITMAPFILEHEADER { /* bmfh */
UINT bfType; DWORD bfSize; UINT bfReserved1; UINT bfReserved2; DWORD bfOffBits;
} BITMAPFILEHEADER;
其中:
bfType |
說明檔案的型別,該值必需是0x4D42,也就是字元'BM'。 |
bfSize |
說明該點陣圖檔案的大小,用位元組為單位 |
bfReserved1 |
保留,必須設定為0 |
bfReserved2 |
保留,必須設定為0 |
bfOffBits |
說明從檔案頭開始到實際的圖象資料之間的位元組的偏移量。這個引數是非常有用的,因為點陣圖資訊頭和調色盤的長度會根據不同情況而變化,所以你可以用這個偏移值迅速的從檔案中讀取到位資料。 |
2、點陣圖資訊頭BITMAPINFOHEADER
結構體定義如下:
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結構所需要的字數。 |
biWidth |
說明圖象的寬度,以象素為單位。 |
biHeight |
說明圖象的高度,以象素為單位。注:這個值除了用於描述影象的高度之外,它還有另一個用處,就是指明該影象是倒向的點陣圖,還是正向的點陣圖。如果該值是一個正數,說明影象是倒向的,如果該值是一個負數,則說明影象是正向的。大多數的BMP檔案都是倒向的點陣圖,也就是時,高度值是一個正數。 |
biPlanes |
為目標裝置說明位面數,其值將總是被設為1。 |
biBitCount |
說明位元數/象素,其值為1、4、8、16、24、或32。但是由於我們平時用到的影象絕大部分是24位和32位的,所以我們討論這兩類影象。 |
biCompression |
說明圖象資料壓縮的型別,同樣我們只討論沒有壓縮的型別:BI_RGB。 |
biSizeImage |
說明圖象的大小,以位元組為單位。當用BI_RGB格式時,可設定為0。 |
biXPelsPerMeter |
說明水平解析度,用象素/米表示。 |
biYPelsPerMeter |
說明垂直解析度,用象素/米表示。 |
biClrUsed |
說明點陣圖實際使用的彩色表中的顏色索引數(設為0的話,則說明使用所有調色盤項)。 |
biClrImportant |
說明對圖象顯示有重要影響的顏色索引的數目,如果是0,表示都重要。 |
3、RGB顏色陣列
有關RGB三色空間我想大家都很熟悉,這裡我想說的是在Windows下,RGB顏色陣列儲存的格式其實BGR。也就是說,對於24位的RGB點陣圖畫素資料格式是:
藍色B值 |
綠色G值 |
紅色R值 |
對於32位的RGB點陣圖畫素資料格式是:
藍色B值 |
綠色G值 |
紅色R值 |
透明通道A值 |
透明通道也稱Alpha通道,該值是該畫素點的透明屬性,取值在0(全透明)到255(不透明)之間。對於24位的影象來說,因為沒有Alpha通道,故整個影象都不透明。
二、搞清了檔案格式,下一步我們要實現載入。
載入檔案的目的是要得到圖片屬性,以及RGB資料,然後可以將其繪製在DC上(GDI),或是生成紋理物件(3D:OpenGL/Direct3D)。這兩種用途在資料處理上有點區別,我們主要按前一種用法講,在和3D有不同的地方,我們再提出來。
1、載入檔案頭
//Load the file header
BITMAPFILEHEADER header;
memset(&header, 0, sizeof(header));
inf.read((char*)&header, sizeof(header));
if(header.bfType != 0x4D42)
return false;
這個很簡單,沒有什麼好說的。
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各重要的圖形屬性:寬,高,以及每個畫素顏色所佔用的位數。
3、行對齊
由於Windows在進行行掃描的時候最小的單位為4個位元組,所以當
圖片寬 X 每個畫素的位元組數 != 4的整數倍
時要在每行的後面補上缺少的位元組,以0填充(一般來說當影象寬度為2的冪時不需要對齊)。點陣圖檔案裡的資料在寫入的時候已經進行了行對齊,也就是說載入的時候不需要再做行對齊。但是這樣一來圖片資料的長度就不是:寬 X 高 X 每個畫素的位元組數 了,我們需要通過下面的方法計算正確的資料長度:
//Calculate the image data size
int iLineByteCnt = (((m_iImageWidth*m_iBitsPerPixel) + 31) >> 5) << 2;
m_iImageDataSize = iLineByteCnt * m_iImageHeight;
4、載入圖片資料
對於24位和32位的點陣圖檔案,點陣圖資料的偏移量為sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER),也就是說現在我們可以直接讀取影象資料了。
if(m_pImageData) delete []m_pImageData;
m_pImageData = new unsigned char[m_iImageDataSize];
inf.read((char*)m_pImageData, m_iImageDataSize);
如果你足夠細心,就會發現記憶體m_pImageData裡的資料的確是BGR格式,可以用個純藍色或者是純紅色的圖片測試一下。
5、繪製
好了,資料和屬性我們都有了,現在就可以拿來隨便用了,就和吃饅頭一樣,愛粘白糖粘白糖,愛粘紅糖粘紅糖。下面是我的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)的不同之處
如果你是想用剛才我們得到的資料生成紋理物件,那麼你還要請出下面的問題。
首先,用來生成紋理的資料不需要對齊,也就是說不能在每行的後面加上對齊的位元組。當然在OpenGL裡要求紋理圖片的尺寸為2的冪,所以這個問題實際上不存在;
其次,我們得到的圖形資料格式是BGR(BGRA),所以在生成紋理的時候,需指定格式為GL_BGR_EXT(GL_BGRA_EXT);否則需要做BGR->RGB(BGRA->RGBA)的轉化。
程式分析:
#include <config.h>
#include <pic_operation.h>
#include <stdlib.h>
#include <string.h>
#pragma pack(push) /* 將當前pack設定壓棧儲存 */
#pragma pack(1) /* 必須在結構體定義之前使用 */
typedef struct tagBITMAPFILEHEADER { /* bmfh */
unsigned short bfType; //bfType 說明檔案的型別,該值必需是0x4D42,也就是字元'BM'。
unsigned long bfSize;//bfSize 說明該點陣圖檔案的大小,用位元組為單位
unsigned short bfReserved1;//bfReserved1 保留,必須設定為0
unsigned short bfReserved2;//bfReserved2保留,必須設定為0
unsigned long bfOffBits;//說明從檔案頭開始到實際的圖象資料之間的位元組的偏移量。這個引數是非常有用的,因為點陣圖資訊頭和調色盤的長度會根據不同情況而變化,所以你可以用這個偏移值迅速的從檔案中讀取到位資料。
} BITMAPFILEHEADER;
typedef struct tagBITMAPINFOHEADER { /* bmih */
unsigned long biSize;//biSize 說明BITMAPINFOHEADER結構所需要的字數
unsigned long biWidth;//biWidth說明圖象的寬度,以象素為單位。
unsigned long biHeight;//說明圖象的高度,以象素為單位。注:這個值除了用於描述影象的高度之外,它還有另一個用處,就是指明該影象是倒向的點陣圖,還是正向的點陣圖。如果該值是一個正數,說明影象是倒向的,如果該值是一個負數,則說明影象是正向的。大多數的BMP檔案都是倒向的點陣圖,也就是時,高度值是一個正數。
unsigned short biPlanes;//為目標裝置說明位面數,其值將總是被設為1。
unsigned short biBitCount;//說明位元數/象素,其值為1、4、8、16、24、或32。但是由於我們平時用到的影象絕大部分是24位和32位的,所以我們討論這兩類影象。
unsigned long biCompression;//說明圖象資料壓縮的型別,同樣我們只討論沒有壓縮的型別:BI_RGB。
unsigned long biSizeImage;//說明圖象的大小,以位元組為單位。當用BI_RGB格式時,可設定為0。
unsigned long biXPelsPerMeter;//說明水平解析度,用象素/米表示
unsigned long biYPelsPerMeter;//說明垂直解析度,用象素/米表示。
unsigned long biClrUsed;//說明點陣圖實際使用的彩色表中的顏色索引數(設為0的話,則說明使用所有調色盤項)。
unsigned long biClrImportant;//說明對圖象顯示有重要影響的顏色索引的數目,如果是0,表示都重要。
} BITMAPINFOHEADER;
#pragma pack(pop) /* 恢復先前的pack設定 */
static int isBMPFormat(unsigned char *aFileHead);
static int GetPixelDatasFrmBMP(unsigned char *aFileHead, PT_PixelDatas ptPixelDatas);
static int FreePixelDatasForBMP(PT_PixelDatas ptPixelDatas);
T_PicFileParser g_tBMPParser = {
.name = "bmp",
.isSupport = isBMPFormat,
.GetPixelDatas = GetPixelDatasFrmBMP,
.FreePixelDatas = FreePixelDatasForBMP,
};
static int isBMPFormat(unsigned char *aFileHead)
{
if (aFileHead[0] != 0x42 || aFileHead[1] != 0x4d)//bfType 說明檔案的型別,該值必需是0x4D42,也就是字元'BM'。
return 0;
else
return 1;
}
static int CovertOneLine(int iWidth, int iSrcBpp, int iDstBpp, unsigned char *pudSrcDatas, unsigned char *pudDstDatas)
{
unsigned int dwRed;
unsigned int dwGreen;
unsigned int dwBlue;
unsigned int dwColor;
unsigned short *pwDstDatas16bpp = (unsigned short *)pudDstDatas;
unsigned int *pwDstDatas32bpp = (unsigned int *)pudDstDatas;
int i;
int pos = 0;
if (iSrcBpp != 24)
{
return -1;
}
if (iDstBpp == 24)
{
memcpy(pudDstDatas, pudSrcDatas, iWidth*3);
}
else
{
for (i = 0; i < iWidth; i++)
{
dwBlue = pudSrcDatas[pos++];
dwGreen = pudSrcDatas[pos++];
dwRed = pudSrcDatas[pos++];
if (iDstBpp == 32)
{
dwColor = (dwRed << 16) | (dwGreen << 8) | dwBlue;
*pwDstDatas32bpp = dwColor;
pwDstDatas32bpp++;
}
else if (iDstBpp == 16)
{
/* 565 */
dwRed = dwRed >> 3;
dwGreen = dwGreen >> 2;
dwBlue = dwBlue >> 3;
dwColor = (dwRed << 11) | (dwGreen << 5) | (dwBlue);
*pwDstDatas16bpp = dwColor;
pwDstDatas16bpp++;
}
}
}
return 0;
}
/*
* ptPixelDatas->iBpp 是輸入的引數, 它決定從BMP得到的資料要轉換為該格式
*/
static int GetPixelDatasFrmBMP(unsigned char *aFileHead, PT_PixelDatas ptPixelDatas)
{
BITMAPFILEHEADER *ptBITMAPFILEHEADER;//檔案資訊頭BITMAPFILEHEADER
BITMAPINFOHEADER *ptBITMAPINFOHEADER;//點陣圖資訊頭BITMAPINFOHEADER
int iWidth;//寬度
int iHeight;//高度
int iBMPBpp;//位寬
int y;
unsigned char *pucSrc;
unsigned char *pucDest;
int iLineWidthAlign;
int iLineWidthReal;
ptBITMAPFILEHEADER = (BITMAPFILEHEADER *)aFileHead;
ptBITMAPINFOHEADER = (BITMAPINFOHEADER *)(aFileHead + sizeof(BITMAPFILEHEADER));
iWidth = ptBITMAPINFOHEADER->biWidth;//寬度
iHeight = ptBITMAPINFOHEADER->biHeight;//高度
iBMPBpp = ptBITMAPINFOHEADER->biBitCount;//位寬
if (iBMPBpp != 24)//如果不是24位的就不支援
{
DBG_PRINTF("iBMPBpp = %d\n", iBMPBpp);
DBG_PRINTF("sizeof(BITMAPFILEHEADER) = %d\n", sizeof(BITMAPFILEHEADER));
return -1;
}
//填充畫素資料描述結構體
ptPixelDatas->iWidth = iWidth;
ptPixelDatas->iHeight = iHeight;
//ptPixelDatas->iBpp = iBpp;
ptPixelDatas->aucPixelDatas = malloc(iWidth * iHeight * ptPixelDatas->iBpp / 8);
ptPixelDatas->iLineBytes = iWidth * ptPixelDatas->iBpp / 8;
if (NULL == ptPixelDatas->aucPixelDatas)
{
return -1;
}
/*由於lcd的座標和點陣圖的座標不一樣,所以要進行下面的變換*/
iLineWidthReal = iWidth * iBMPBpp / 8;
iLineWidthAlign = (iLineWidthReal + 3) & ~0x3; /* 由於要進行向4對齊,所以向4取整 ,真正的長度進行對齊*/
pucSrc = aFileHead + ptBITMAPFILEHEADER->bfOffBits;
pucSrc = pucSrc + (iHeight - 1) * iLineWidthAlign;
pucDest = ptPixelDatas->aucPixelDatas;//要把資料放在哪裡
for (y = 0; y < iHeight; y++)
{
//memcpy(pucDest, pucSrc, iLineWidthReal);
CovertOneLine(iWidth, iBMPBpp, ptPixelDatas->iBpp, pucSrc, pucDest);
pucSrc -= iLineWidthAlign;
pucDest += ptPixelDatas->iLineBytes;
}
return 0;
}
static int FreePixelDatasForBMP(PT_PixelDatas ptPixelDatas)
{
free(ptPixelDatas->aucPixelDatas);
return 0;
}