bmp文件格式詳細解析

先區分幾個概念:16色和16位色一樣嗎?
不一樣!
顏色位數,即是用多少位字節表示的值,每一位可以表示0和1兩值。通常圖片的顏色深度,簡稱色深,就是用位數來表示的,所以,我通常會看到8位色,16位色,24位色和32位色。
而我們在其它地方看到的又是16色,256色,16777216色等等,這些怎麼一回事呢? 
16色即代表16種顏色,256色即256種顏色,8位色就是用8個位來表示的顏色,即2的8次方,就是256色,16位色2的16次方,就是65536色,24位即16777216色,32位即4294967296色 
他們之間並不存在轉換關係,只有兼容關係,你不可能將紅色轉換爲藍色,也不可能將一個黑白的圖片轉換成一個彩色圖片,但可以將彩色圖片變成黑白圖片,就像以前的黑白電視機播放彩色錄像始終是黑白的,而彩色電視機播放黑白的錄像還是黑白的,當由多到少時,它會丟失顏色數據,當由少到多時,並不會還原丟失的數據。
好了,切入正題:
bmp文件結構解析:
一個bmp圖片最多由4大部分組成:BITMAPFILEHEADER結構體,BITMAPINFOHEADER結構體,RGBQUAD結構體(這個結構體可以有,也可以沒有),DIB數據區。其中DIB意思就是Device-Independent Bitmap(設備無關位圖)。

一個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個屬性分別是bfReserved1bfReserved2(2字節),這裏是2個保留屬性,都爲0,這裏等於&H00000x0000

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位圖片表示用4bit來表示 一個顏色信息。)一個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字節空間,從左到右每個字節依次表示(藍色,綠色,紅色,未使用)。舉例的這個圖片我數了數總共有16RGBQUAD 構體,由於該圖片是4位圖,2^4正好等於16,所以它把16種顏色全部都枚舉出來了,這些顏色就是一個顏色索引表。顏色索引表編號從0開始,總共16 顏色,所以編號爲0-15。從winhex中可以看到按照順序,這16RGBQUAD結構體依次爲:

編號:(藍,綠,紅,空)

0號:(00000000)

1號:(00008000)

2號:(00800000)

3號:(00808000)

4號:(80000000)

5號:(80008000)

6號:(80800000)

7號:(80808000)

8號:(C0C0C000)

9號:(0000FF00)

10號:(00FF0000)

11號:(00FFFF00)

12號:(FF000000)

13號:(FF00FF00)

14號:(FFFF0000)

15號:(FFFFFF00)

到這裏,正好滿足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");

} 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章