而我們在其它地方看到的又是16色,256色,16777216色等等,這些怎麼一回事呢?
16色即代表16種顏色,256色即256種顏色,8位色就是用8個位來表示的顏色,即2的8次方,就是256色,16位色2的16次方,就是65536色,24位即16777216色,32位即4294967296色
他們之間並不存在轉換關係,只有兼容關係,你不可能將紅色轉換爲藍色,也不可能將一個黑白的圖片轉換成一個彩色圖片,但可以將彩色圖片變成黑白圖片,就像以前的黑白電視機播放彩色錄像始終是黑白的,而彩色電視機播放黑白的錄像還是黑白的,當由多到少時,它會丟失顏色數據,當由少到多時,並不會還原丟失的數據。
一個bmp文件以BITMAPFILEHEADER結構體開始,
typedef struct tagBITMAPFILEHEADER {
WORD bfType;//固定爲0x4d42;
DWORD bfSize; //文件大小
WORD bfReserved1; //保留字,不考慮
WORD bfReserved2; //保留字,同上
DWORD bfOffBits; //實際位圖數據的偏移字節數,即前三個部分長度之和
} BITMAPFILEHEADER;
BITMAPFILEHEADER的第1個屬性是bfType(2字節),這裏恆定等於0x4D42。由於內存中的數據排列高位在左,低位在右,所以內存中從左往右看就顯示成(42 4D),所以在winhex中頭兩個 字節顯示爲(42 4D)就是這樣形成的,以後的數據都是這個特點,不再作重複說明。
BITMAPFILEHEADER的第2個屬性是bfSize(4字節),表示整個bmp文件的大小。
BITMAPFILEHEADER的第3個、第4個屬性分別是bfReserved1、bfReserved2(各2字節),這裏是2個保留屬性,都爲0,這裏等於&H0000、0x0000。
BITMAPFILEHEADER的第5個屬性是bfOffBits(4字節),表示DIB數據區在bmp文件中的位置偏移量,比如等於0x00000076=118,表示數據區從文件開始往後數的118字節開始。
BITMAPFILEHEADER結構體這裏就講完了,大家會發現BITMAPFILEHEADER只佔了bmp文件開始的14字節長度,但需要 特別說明的是:我們在編程時,經常是以二進制的形式打開一個bmp文件,然後將其開頭的14個字節讀入自己定義的BITMAPFILEHEADER結構體中,如果按上述定義結構體的方式,需要注意,這個自己定義的結構體在內存中由於字節對齊,會佔用16字節的空間,因而直接讀入16字節,按字節順序賦值給結構體,出來的數據是錯誤的,這樣的話,可以先將這14字節的數據讀入到一個緩衝器中,然後從緩衝器中按字節對結構體進行賦值。詳細程序見後附錄。鑑於此中原因,在vb中定義一個BITMAPFILEHEADER結構體變量,其長度佔了16個字節,原因就是第1個屬性本來應該只分配2個字節,但實際被 分配了4個字節,多出來2個字節,所以如果想保存一張bmp圖片,寫入BITMAPFILEHEADER結構體時一定要注意這一點。
接下來是BITMAPINFO結構體部分。BITMAPINFO段由兩部分組成:BITMAPINFOHEADER結構體和RGBQUAD結構 體。其中RGBQUAD結構體表示圖片的顏色信息,有些時候可以省略,一般的24位圖片和32位圖片都不帶RGBQUAD結構體,因爲DIB數據區直接表 示的RGB值,一般4位圖片和8位圖片才帶有RGBQUAD結構體。(多少位的圖片就是用多少位來表示一個顏色信息,例如4位圖片表示用4個bit來表示 一個顏色信息。)一個bmp文件中有沒有RGBQUAD結構體,可以根據前面BITMAPFILEHEADER結構體的第5個屬性bfOffBits來判 斷,因爲BITMAPINFOHEADER結構體長度爲40bit,如果BITMAPINFOHEADER結構體結束後還未到DIB數據區的偏移量,就說 明接下來的數據是RGBQUAD結構體部分。
下面進入正題BITMAPINFOHEADER部分。
typedef struct tagBITMAPINFOHEADER{
//public:
DWORD biSize; //指定此結構體的長度,爲40
LONG biWidth; //位圖寬
LONG biHeight; //位圖高
WORD biPlanes; //平面數,爲1
WORD biBitCount; //採用顏色位數,可以是1,2,4,8,16,24,新的可以是32
DWORD biCompression; //壓縮方式,可以是0,1,2,其中0表示不壓縮
DWORD biSizeImage; //實際位圖數據佔用的字節數
LONG biXPelsPerMeter; //X方向分辨率
LONG biYPelsPerMeter; //Y方向分辨率
DWORD biClrUsed; //使用的顏色數,如果爲0,則表示默認值(2^顏色位數)
DWORD biClrImportant; //重要顏色數,如果爲0,則表示所有顏色都是重要的
} BITMAPINFOHEADER;
BITMAPINFOHEADER的第1個屬性是biSize(4字節),表示BITMAPINFOHEADER結構體的長度,最常見的長度是40字節。
BITMAPINFOHEADER的第2個屬性是biWidth(4字節),表示bmp圖片的寬度
BITMAPINFOHEADER的第3個屬性是biHeight(4字節),表示bmp圖片的高度
BITMAPINFOHEADER的第4個屬性是biPlanes(2字節),表示bmp圖片的平面屬,顯然顯示器只有一個平面,所以恆等於1,這裏等於0x0001。
BITMAPINFOHEADER的第5個屬性是biBitCount(2字節),表示bmp圖片的顏色位數,即1位圖(單色或二值圖像),8位圖,16位圖,24位圖、32位圖等等。
BITMAPINFOHEADER的第6個屬性是biCompression(4字節),表示圖片的壓縮屬性,bmp圖片是不壓縮的,等於0,所以這裏爲0x00000000。
BITMAPINFOHEADER的第7個屬性是biSizeImage(4字節),表示bmp圖片數據區的大小,當上一個數值biCompression等於0時,這裏的值可以省略不填,所以這裏等於0x00000000。
BITMAPINFOHEADER的第8個屬性是biXPelsPerMeter(4字節),表示圖片X軸每米多少像素,可省略,這裏等於0x00000EC3=3779像素/米。
BITMAPINFOHEADER的第9個屬性是biYPelsPerMeter(4字節),表示圖片Y軸每米多少像素,可省略,這裏等於0x00000EC3=3779像素/米。
BITMAPINFOHEADER的第10個屬性是biClrUsed(4字節),表示使用了多少個顏色索引表,一般biBitCount屬性小於16纔會用到,等於0時表示有2^biBitCount個顏色索引表,所以這裏仍等於0x00000000。
BITMAPINFOHEADER的第11個屬性是biClrImportant(4字節),表示有多少個重要的顏色,等於0時表示所有顏色都很重要,所以這裏等於0x00000000。
至此BITMAPINFOHEADER結構體結束。
由於這個圖片到這裏還未到達DIB數據區的偏移量,或者說由於BITMAPINFOHEADER的第5個屬性是biBitCount<16,也就是在1位圖(只有兩種顏色),4位圖(只有2^4=16種顏色),8位圖(只有2^8=256種顏色)的情況下,此時會有顏色表,也就是接下來的部分:RGBQUAD結構體。
//調色板Palette,當然,這裏是對那些需要調色板的位圖文件而言的。24位和32位是不需要調色板的。
//(調色板結構體個數等於使用的顏色數,即是多少色圖就有多少個,4位圖16色,就有16個RGBQUAD結構體。)
typedef struct tagRGBQUAD {
//public:
BYTE rgbBlue; //該顏色的藍色分量
BYTE rgbGreen; //該顏色的綠色分量
BYTE rgbRed; //該顏色的紅色分量
BYTE rgbReserved; //保留值
} RGBQUAD;
這裏舉個4位圖也就是16色圖的例子:一 個RGBQUAD結構體只佔用4字節空間,從左到右每個字節依次表示(藍色,綠色,紅色,未使用)。舉例的這個圖片我數了數總共有16個RGBQUAD結 構體,由於該圖片是4位圖,2^4正好等於16,所以它把16種顏色全部都枚舉出來了,這些顏色就是一個顏色索引表。顏色索引表編號從0開始,總共16個 顏色,所以編號爲0-15。從winhex中可以看到按照順序,這16個RGBQUAD結構體依次爲:
編號:(藍,綠,紅,空)
0號:(00,00,00,00)
1號:(00,00,80,00)
2號:(00,80,00,00)
3號:(00,80,80,00)
4號:(80,00,00,00)
5號:(80,00,80,00)
6號:(80,80,00,00)
7號:(80,80,80,00)
8號:(C0,C0,C0,00)
9號:(00,00,FF,00)
10號:(00,FF,00,00)
11號:(00,FF,FF,00)
12號:(FF,00,00,00)
13號:(FF,00,FF,00)
14號:(FF,FF,00,00)
15號:(FF,FF,FF,00)
到這裏,正好滿足DIB數據區的偏移量,所以後面的字節就是圖片內容了。這裏需要提醒的是所有的DIB數據掃描行是上下顛倒的,也就是說一幅圖片先繪製底部的像素,再繪製頂部的像素,所以這些DIB數據所表示的像素點就是從圖片的左下角開始,一直表示到圖片的右上角。
程序附錄:
// std.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
//ReadBitMap
//
#include
#include
#include
#include
#include
#define WIDTHBYTES(bits) (((bits)+31)/32*4)
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned long DWORD;
typedef long LONG;
//位圖文件頭信息結構定義
//其中不包含文件類型信息(由於結構體的內存結構決定,要是加了的話將不能正確讀取文件信息)
typedef struct tagBITMAPFILEHEADER {
WORD bfType;//固定爲0x4d42
DWORD bfSize; //文件大小
WORD bfReserved1; //保留字,不考慮
WORD bfReserved2; //保留字,同上
DWORD bfOffBits; //實際位圖數據的偏移字節數,即前三個部分長度之和
} BITMAPFILEHEADER;
//信息頭BITMAPINFOHEADER,也是一個結構,其定義如下:
typedef struct tagBITMAPINFOHEADER{
//public:
DWORD biSize; //指定此結構體的長度,爲40
LONG biWidth; //位圖寬
LONG biHeight; //位圖高
WORD biPlanes; //平面數,爲1
WORD biBitCount; //採用顏色位數,可以是1,2,4,8,16,24,新的可以是32
DWORD biCompression; //壓縮方式,可以是0,1,2,其中0表示不壓縮
DWORD biSizeImage; //實際位圖數據佔用的字節數
LONG biXPelsPerMeter; //X方向分辨率
LONG biYPelsPerMeter; //Y方向分辨率
DWORD biClrUsed; //使用的顏色數,如果爲0,則表示默認值(2^顏色位數)
DWORD biClrImportant; //重要顏色數,如果爲0,則表示所有顏色都是重要的
} BITMAPINFOHEADER;
//調色板Palette,當然,這裏是對那些需要調色板的位圖文件而言的。24位和32位是不需要調色板的。
//(似乎是調色板結構體個數等於使用的顏色數。)
typedef struct tagRGBQUAD {
//public:
BYTE rgbBlue; //該顏色的藍色分量
BYTE rgbGreen; //該顏色的綠色分量
BYTE rgbRed; //該顏色的紅色分量
BYTE rgbReserved; //保留值
} RGBQUAD;
void showBmpHead(BITMAPFILEHEADER* pBmpHead)
{
printf("位圖文件頭:\n");
printf("bmp格式標誌bftype:0x%x\n",pBmpHead->bfType );
printf("文件大小:%d\n",pBmpHead->bfSize);
printf("保留字:%d\n",pBmpHead->bfReserved1);
printf("保留字:%d\n",pBmpHead->bfReserved2);
printf("實際位圖數據的偏移字節數:%d\n",pBmpHead->bfOffBits);
}
void showBmpInforHead(tagBITMAPINFOHEADER* pBmpInforHead)
{
printf("位圖信息頭:\n");
printf("結構體的長度:%d\n",pBmpInforHead->biSize);
printf("位圖寬:%d\n",pBmpInforHead->biWidth);
printf("位圖高:%d\n",pBmpInforHead->biHeight);
printf("biPlanes平面數:%d\n",pBmpInforHead->biPlanes);
printf("biBitCount採用顏色位數:%d\n",pBmpInforHead->biBitCount);
printf("壓縮方式:%d\n",pBmpInforHead->biCompression);
printf("biSizeImage實際位圖數據佔用的字節數:%d\n",pBmpInforHead->biSizeImage);
printf("X方向分辨率:%d\n",pBmpInforHead->biXPelsPerMeter);
printf("Y方向分辨率:%d\n",pBmpInforHead->biYPelsPerMeter);
printf("使用的顏色數:%d\n",pBmpInforHead->biClrUsed);
printf("重要顏色數:%d\n",pBmpInforHead->biClrImportant);
}
void showRgbQuan(tagRGBQUAD* pRGB)
{
printf("(%-3d,%-3d,%-3d) ",pRGB->rgbRed,pRGB->rgbGreen,pRGB->rgbBlue);
}
void main()
{
BITMAPFILEHEADER bitHead;
BITMAPINFOHEADER bitInfoHead;
FILE* pfile;
char strFile[50];
char *BmpFileHeader;
WORD *temp_WORD;
DWORD *temp_DWORD;
printf("please input the .bmp file name:\n");
scanf("%s",strFile);
pfile = fopen(strFile,"rb");//打開文件
BmpFileHeader=(char *)calloc(14,sizeof(char));
if(pfile!=NULL)
{
printf("file bkwood.bmp open success.\n");
//讀取位圖文件頭信息
fread(BmpFileHeader,1,14,pfile);
temp_WORD=(WORD* )(BmpFileHeader);
bitHead.bfType=*temp_WORD;
if(bitHead.bfType != 0x4d42)
{
printf("file is not .bmp file!");
return;
}
temp_DWORD=(DWORD *)(BmpFileHeader+sizeof(bitHead.bfType));
bitHead.bfSize=*temp_DWORD;
temp_WORD=(WORD*)(BmpFileHeader+sizeof(bitHead.bfType)+sizeof(bitHead.bfSize));
bitHead.bfReserved1=*temp_WORD;
temp_WORD=(WORD*)(BmpFileHeader+sizeof(bitHead.bfType)+sizeof(bitHead.bfSize)+sizeof(bitHead.bfReserved1));
bitHead.bfReserved2=*temp_WORD;
temp_DWORD=(DWORD*)(BmpFileHeader+sizeof(bitHead.bfType)+sizeof(bitHead.bfSize)+sizeof(bitHead.bfReserved1)+sizeof(bitHead.bfReserved2));
bitHead.bfOffBits=*temp_DWORD;
showBmpHead(&bitHead);
printf("\n\n");
//讀取位圖信息頭信息
fread(&bitInfoHead,1,sizeof(BITMAPINFOHEADER),pfile);
showBmpInforHead(&bitInfoHead);
printf("\n");
}
else
{
printf("file open fail!\n");
return;
}
tagRGBQUAD *pRgb ;
if(bitInfoHead.biBitCount < 24)//有調色板
{
//讀取調色盤結信息
long nPlantNum = long(pow(2,double(bitInfoHead.biBitCount))); // Mix color Plant Number;
pRgb=(tagRGBQUAD *)malloc(nPlantNum*sizeof(tagRGBQUAD));
memset(pRgb,0,nPlantNum*sizeof(tagRGBQUAD));
int num = fread(pRgb,4,nPlantNum,pfile);
printf("Color Plate Number: %d\n",nPlantNum);
printf("顏色板信息:\n");
for (int i =0; i<nplantnum;i++)
{
if (i%5==0)
{
printf("\n");
}
showRgbQuan(&pRgb[i]);
}
printf("\n");
}
int width = bitInfoHead.biWidth;
int height = bitInfoHead.biHeight;
//分配內存空間把源圖存入內存
int l_width = WIDTHBYTES(width* bitInfoHead.biBitCount);//計算位圖的實際寬度並確保它爲32的倍數
BYTE *pColorData=(BYTE *)malloc(height*l_width);
memset(pColorData,0,height*l_width);
long nData = height*l_width;
//把位圖數據信息讀到數組裏
fread(pColorData,1,nData,pfile);
//將位圖數據轉化爲RGB數據
tagRGBQUAD* dataOfBmp;
dataOfBmp = (tagRGBQUAD *)malloc(width*height*sizeof(tagRGBQUAD));//用於保存各像素對應的RGB數據
memset(dataOfBmp,0,width*height*sizeof(tagRGBQUAD));
if(bitInfoHead.biBitCount<24)//有調色板,即位圖爲非真彩色
{
int k;
int index = 0;
if (bitInfoHead.biBitCount == 1)
{
for(int i=0;i<height;i++)
for(int j=0;j<width;j++)
{
BYTE mixIndex= 0;
k = i*l_width + j/8;//k:取得該像素顏色數據在實際數據數組中的序號
//j:提取當前像素的顏色的具體值
mixIndex = pColorData[k];
switch(j%8)
{
case 0:
mixIndex = mixIndex<<7;
mixIndex = mixIndex>>7;
break;
case 1:
mixIndex = mixIndex<<6;
mixIndex = mixIndex>>7;
break;
case 2:
mixIndex = mixIndex<<5;
mixIndex = mixIndex>>7;
break;
case 3:
mixIndex = mixIndex<<4;
mixIndex = mixIndex>>7;
break;
case 4:
mixIndex = mixIndex<<3;
mixIndex = mixIndex>>7;
break;
case 5:
mixIndex = mixIndex<<2;
mixIndex = mixIndex>>7;
break;
case 6:
mixIndex = mixIndex<<1;
mixIndex = mixIndex>>7;
break;
case 7:
mixIndex = mixIndex>>7;
break;
}
//將像素數據保存到數組中對應的位置
dataOfBmp[index].rgbRed = pRgb[mixIndex].rgbRed;
dataOfBmp[index].rgbGreen = pRgb[mixIndex].rgbGreen;
dataOfBmp[index].rgbBlue = pRgb[mixIndex].rgbBlue;
dataOfBmp[index].rgbReserved = pRgb[mixIndex].rgbReserved;
index++;
}
}
if(bitInfoHead.biBitCount==2)
{
for(int i=0;i<height;i++)
for(int j=0;j<width;j++)
{
BYTE mixIndex= 0;
k = i*l_width + j/4;//k:取得該像素顏色數據在實際數據數組中的序號
//j:提取當前像素的顏色的具體值
mixIndex = pColorData[k];
switch(j%4)
{
case 0:
mixIndex = mixIndex<<6;
mixIndex = mixIndex>>6;
break;
case 1:
mixIndex = mixIndex<<4;
mixIndex = mixIndex>>6;
break;
case 2:
mixIndex = mixIndex<<2;
mixIndex = mixIndex>>6;
break;
case 3:
mixIndex = mixIndex>>6;
break;
}
//將像素數據保存到數組中對應的位置
dataOfBmp[index].rgbRed = pRgb[mixIndex].rgbRed;
dataOfBmp[index].rgbGreen = pRgb[mixIndex].rgbGreen;
dataOfBmp[index].rgbBlue = pRgb[mixIndex].rgbBlue;
dataOfBmp[index].rgbReserved = pRgb[mixIndex].rgbReserved;
index++;
}
}
if(bitInfoHead.biBitCount == 4)
{
for(int i=0;i<height;i++)
for(int j=0;j<width;j++)
{
BYTE mixIndex= 0;
k = i*l_width + j/2;
mixIndex = pColorData[k];
if(j%2==0)
{//低
mixIndex = mixIndex<<4;
mixIndex = mixIndex>>4;
}
else
{//高
mixIndex = mixIndex>>4;
}
dataOfBmp[index].rgbRed = pRgb[mixIndex].rgbRed;
dataOfBmp[index].rgbGreen = pRgb[mixIndex].rgbGreen;
dataOfBmp[index].rgbBlue = pRgb[mixIndex].rgbBlue;
dataOfBmp[index].rgbReserved = pRgb[mixIndex].rgbReserved;
index++;
}
}
if(bitInfoHead.biBitCount == 8)
{
for(int i=0;i<height;i++)
for(int j=0;j<width;j++)
{
BYTE mixIndex= 0;
k = i*l_width + j;
mixIndex = pColorData[k];
dataOfBmp[index].rgbRed = pRgb[mixIndex].rgbRed;
dataOfBmp[index].rgbGreen = pRgb[mixIndex].rgbGreen;
dataOfBmp[index].rgbBlue = pRgb[mixIndex].rgbBlue;
dataOfBmp[index].rgbReserved = pRgb[mixIndex].rgbReserved;
index++;
}
}
if(bitInfoHead.biBitCount == 16)
{
for(int i=0;i<height;i++)
for(int j=0;j<width;j++)
{
WORD mixIndex= 0;
k = i*l_width + j*2;
WORD shortTemp;
shortTemp = pColorData[k+1];
shortTemp = shortTemp<<8;
mixIndex = pColorData[k] + shortTemp;
dataOfBmp[index].rgbRed = pRgb[mixIndex].rgbRed;
dataOfBmp[index].rgbGreen = pRgb[mixIndex].rgbGreen;
dataOfBmp[index].rgbBlue = pRgb[mixIndex].rgbBlue;
dataOfBmp[index].rgbReserved = pRgb[mixIndex].rgbReserved;
index++;
}
}
}
else//位圖爲24位真彩色
{
int k;
int index = 0;
for(int i=0;i<height;i++)
for(int j=0;j<width;j++)
{
k = i*l_width + j*3;
dataOfBmp[index].rgbRed = pColorData[k+2];
dataOfBmp[index].rgbGreen = pColorData[k+1];
dataOfBmp[index].rgbBlue = pColorData[k];
index++;
}
}
printf("像素數據信息:\n");
/*
for (int i=0; i<width*height; i++)
{
if (i%5==0)
{
printf("\n");
}
showRgbQuan(&dataOfBmp[i]);
}
*/
fclose(pfile);
if (bitInfoHead.biBitCount<24)
{
free(pRgb);
}
free(dataOfBmp);
free(pColorData);
free(BmpFileHeader);
printf("\n");
}