BMP文件讀寫筆記


一、簡介

BMP文件格式,是Windows系統中廣泛使用的圖像文件格式。由於它可以不作任何變換地保存圖像像素域的數據,因此成為我們取得原始數據的重要來源。Windows的圖形用戶界面(graphical user interfaces)也在它的內建圖像子系統GDI中對BMP格式提供了支持。

BMP文件的數據按照從文件頭開始的先后順序分為四個部分:

  • bmp文件頭(bmp file header)提供文件的格式、大小等信息
  • 位圖信息頭(bitmap information)提供圖像數據的尺寸、位平面數、壓縮方式、顏色索引等信息
  • 調色板(color palette):可選,如使用索引來表示圖像,調色板就是索引與其對應的顏色的映射表
  •  位圖數據(bitmap data)就是圖像數據啦

 

二、文件各個部分詳細描述(以字節文單位):

1、圖像文件頭
1)1-2:圖像文件標識符。0x4d42=’BM’,表示是Windows支持的BMP格式。
2)3-6:整個文件大小。4690 0000,為00009046h=36934。
3)7-8:保留,必須設置為0。
4)9-10:保留,必須設置為0。
5)11-14:從文件開始到位圖數據之間的偏移量(14+40+4*(2^biBitCount))(在有顏色板的情況下)。4600 0000,為00000046h=70,上面的文件頭就是35字=70字節。
其結構定義如下: 
typedef   struct   tagBITMAPFILEHEADER 

WORDbfType;   //   位圖文件的類型,必須為BM 
DWORD   bfSize;   //   位圖文件的大小,以字節為單位   
WORDbfReserved1;   //   位圖文件保留字,必須為0 
WORDbfReserved2;   //   位圖文件保留字,必須為0 
DWORD   bfOffBits;   //   位圖數據的起始位置,以相對於位圖 
//   文件頭的偏移量表示,以字節為單位 
}   BITMAPFILEHEADER; 
 
2、位圖信息頭
6)15-18:位圖圖信息頭長度。
7) 19-22:位圖寬度,以像素為單位。8000 0000,為00000080h=128。
8)23-26:位圖高度,以像素為單位。9000 0000,為00000090h=144。
9)27-28:位圖的位面數,該值總是1。0100,為0001h=1。
10)29-30:每個像素的位數。有1(單色),4(16色),8(256色),16(64K色,高彩色),24(16M色,真彩色),32(4096M色,增強型真彩色)。1000為0010h=16。
11)31-34:壓縮說明:有0(不壓縮),1(RLE 8,8位RLE壓縮),2(RLE 4,4位RLE壓縮,3(Bitfields,位域存放)。RLE簡單地說是采用像素數+像素值的方式進行壓縮(行程編碼)。T408采用的是位域存放方式,用兩個字節表示一個像素,位域分配為r5b6g5。圖中0300 0000為00000003h=3(這張圖片不存在顏色板)。
12)35-38:用字節數表示的位圖數據的大小,該數必須是4的倍數,數值上等於:一行所占的字節數×位圖高度。0090 0000為00009000h=80×90×2h=36864。假設位圖是24位,寬為41,高為30,則數值= (biWidth*biBitCount+31)/32*4*biHeight,即=(41*24+31)/32*4*30=3720
13)39-42:用象素/米表示的水平分辨率。A00F 0000為0000 0FA0h=4000。
14)43-46:用象素/米表示的垂直分辨率。A00F 0000為0000 0FA0h=4000。
15)47-50:位圖使用的顏色索引數。設為0的話,則說明使用所有調色板項。
16)51-54:對圖象顯示有重要影響的顏色索引的數目。如果是0,表示都重要。
 
typedef   struct   tagBITMAPINFOHEADER{ 
DWORD   biSize;   //   本結構所占用字節數 
LONGbiWidth;   //   位圖的寬度,以像素為單位 
LONGbiHeight;   //   位圖的高度,以像素為單位 
WORD   biPlanes;   //   目標設備的級別,必須為1 
WORD   biBitCount//   每個像素所需的位數,必須是1(雙色), 
//   4(16色),8(256色)或24(真彩色)之一 
DWORD   biCompression;   //   位圖壓縮類型,必須是   0(不壓縮), 
//   1(BI_RLE8壓縮類型)或2(BI_RLE4壓縮類型)之一 
DWORD   biSizeImage;   //   位圖的大小,以字節為單位 
LONGbiXPelsPerMeter;   //   位圖水平分辨率,每米像素數 
LONGbiYPelsPerMeter;   //   位圖垂直分辨率,每米像素數 
DWORD   biClrUsed;//   位圖實際使用的顏色表中的顏色數 
DWORD   biClrImportant;//   位圖顯示過程中重要的顏色數 
}   BITMAPINFOHEADER; 
 
3、彩色板
17)(55+0)到(50-1+2^biBitCount):彩色板規范。對於調色板中的每個表項,用下述方法來描述RGB的值:
  • 1字節用於藍色分量
  • 1字節用於綠色分量
  • 1字節用於紅色分量
  • 1字節用於填充符(設置為0,透明度)
對於24-位真彩色圖像就不使用彩色板,因為位圖中的RGB值就代表了每個象素的顏色。
如,彩色板為00F8 0000 E007 0000 1F00 0000 0000 0000,其中:
00F8為F800h = 1111 1000 0000 0000(二進制),是藍色分量的掩碼。
E007 為 07E0h = 0000 0111 1110 0000(二進制),是綠色分量的掩碼。
1F00為001Fh = 0000 0000 0001  1111(二進制),是紅色分量的掩碼。
0000 總設置為0。
將掩碼跟像素值進行“與”運算再進行移位操作就可以得到各色分量值。看看掩碼,就可以明白事實上在每個像素值的兩個字節16位中,按從高到低取5、6、5位分別就是r、g、b分量值。取出分量值后把r、g、b值分別乘以8、4、8就可以補齊第個分量為一個字節,再把這三個字節按rgb組合,放入存儲器(同樣要反序),就可以轉換為24位標准BMP格式了。
顏色表結構的定義如下: 
typedef   struct   tagRGBQUAD   { 
BYTErgbBlue;//   藍色的亮度(值范圍為0-255) 
BYTErgbGreen;   //   綠色的亮度(值范圍為0-255) 
BYTErgbRed;   //   紅色的亮度(值范圍為0-255) 
BYTErgbReserved;//   保留,必須為0 
}   RGBQUAD; 
顏色表中結構數據的個數有biBitCount來確定: 
當biBitCount=1,4,8時,分別有2,16,256個表項; 
當biBitCount=24時,沒有顏色表項。 
位圖信息頭和顏色表組成位圖信息,BITMAPINFO結構定義如下: 
typedef   struct   tagBITMAPINFO   { 
BITMAPINFOHEADER   bmiHeader;   //   位圖信息頭 
RGBQUAD   bmiColors[1];   //   顏色表 
}   BITMAPINFO; 
 
4、圖像數據陣列
位圖數據記錄了位圖的每一個像素值,記錄順序是在掃描行內是從左到右,掃描行之間是從下到上。位圖的一個像素值所占的字節數: 
當biBitCount=1時,8個像素占1個字節; 
當biBitCount=4時,2個像素占1個字節; 
當biBitCount=8時,1個像素占1個字節; 
當biBitCount=24時,1個像素占3個字節; 
Windows規定一個掃描行所占的字節數必須是 
4的倍數(即以long為單位),不足的以0填充, 
一個掃描行所占的字節數計算方法: 
DataSizePerLine=   (biWidth*   biBitCount+31)/8;   
//   一個掃描行所占的字節數 
DataSizePerLine=   DataSizePerLine/4*4;   //   字節數必須是4的倍數 
 

三、通過C語言結構體定義說明BMP主要的四個部分

1、bmp文件頭

1 typedef struct
2 {
3     unsigned short    bfType; //文件類型:0x4D42(十六進制ASCII碼'BM'
4     unsigned long    bfSize;//文件大小,字節單位表示
5     unsigned short    bfReserved1;//保留,必須設0
6     unsigned short    bfReserved2;//保留,必須設0
7     unsigned long    bfOffBits;//說明從文件頭到實際圖像數據之間的偏移量
8 } ClBitMapFileHeader;

2、位圖信息頭

 1 typedef struct
 2 {
 3     unsigned long  biSize;//說明位圖信息頭結構所需要的字節數
 4     long   biWidth;//列出,像素為單位
 5     long   biHeight;//行數,像素為單位
 6     unsigned short   biPlanes;//為目標設備說明位面數,其值將總是被設為1
 7     unsigned short   biBitCount;//說明比特數/象素,其值為1、4、8、16、24、或32,平時用到的圖像絕大部分是24位(真彩色)和8位256色
 8     unsigned long  biCompression;//說明圖象數據壓縮的類型,一般沒有沒有壓縮的類型,所以沒有此項數據    
 9     unsigned long  biSizeImage;//說明圖象的大小,以字節為單位
10     long   biXPelsPerMeter;//說明水平分辨率,用象素/米表示
11     long   biYPelsPerMeter;//說明垂直分辨率,用象素/米表示
12     unsigned long   biClrUsed;//說明位圖實際使用的彩色表中的顏色索引數(設為0的話,則說明使用所有調色板項)。
13     unsigned long   biClrImportant;//說明對圖象顯示有重要影響的顏色索引的數目,如果是0,表示都重要
14 } ClBitMapInfoHeader;

3、調色板

1 typedef struct
2 {
3     unsigned char rgbBlue; //該顏色的藍色分量  
4     unsigned char rgbGreen; //該顏色的綠色分量  
5     unsigned char rgbRed; //該顏色的紅色分量  
6     unsigned char rgbReserved; //保留值  
7 } ClRgbQuad;

 

4、位圖數據

在文件頭、位圖信息頭和調色板(如果有的話)之后,就是位圖數據陣列了,

1 typedef struct
2 {
3     int width;  //寬度,像素單位
4     int height;//高度,像素單位
5     int channels;//通道數
6     unsigned char* imageData;//圖像數據
7 }ClImage;

四、讀取BMP文件

位圖的一個像素值所占的字節數不同時,每個像素的存儲大小不同,讀取位圖圖像數據時需要對不同的位圖采用不同的處理,8位和24位為常用的兩種類型,這里首先介紹,1位的二值圖一個字節表示8個像素信息,需要通過位運算解析出每個像素的值(0或1),通過在對8位位圖讀取方法進行擴充實現。

1)8bit位圖讀取

 1 if (bmpInfoHeader.biBitCount == 8)//256色位圖
 2         {
 3             channels = 1;
 4             //每行數據量是4字節整數倍
 5             offset = (channels*width) % 4;
 6             //計算每行需要在有效數據后填充的無意義字節數
 7             if (offset != 0)
 8             {
 9                 offset = 4 - offset;
10             }
11             bmpImg->channels = 1;
12             bmpImg->imageData = (unsigned char*)malloc(sizeof(unsigned char)*width*height);
13             step = channels*width; //每行寬度(字節為單位)
14             quad = (ClRgbQuad*)malloc(sizeof(ClRgbQuad) * 256); 
15             //讀取調色板,8位字符調色板包含256種顏色
16             fread(quad, sizeof(ClRgbQuad), 256, pFile);
17             free(quad);
18             //讀每個像素的值
19             for (i = 0; i < height; i++)
20             {
21                 for (j = 0; j < width; j++)
22                 {
23                     fread(&pixVal, sizeof(unsigned char), 1, pFile);
24                     // 坐標原點在左下角=》(height - 1 - i)*step+j,通過這個變換坐標原點變為左上角
25                     bmpImg->imageData[(height - 1 - i)*step + j] = pixVal;
26                 }
27                //讀行末尾無意義的字節
28                 if (offset != 0)
29                 {
30                     for (j = 0; j < offset; j++)
31                     {
32                         fread(&pixVal, sizeof(unsigned char), 1, pFile);  //讀取每行的空字節
33                     }
34                 }
35             }
36         }

2)24bit位圖讀取

 1  if (bmpInfoHeader.biBitCount == 24)
 2         {
 3             //3通道, 每個像元占3列
 4             channels = 3;
 5             bmpImg->channels = 3; 
 6              //每個像元占3列,數據塊是8位的3倍
 7             bmpImg->imageData = (unsigned char*)malloc(sizeof(unsigned char)*width * 3 * height);
 8             step = channels*width;//每行寬度,字節為單位(只包括有效數據)
 9              //計算空白數據,因為每行的長度只能是4的整數倍,如果不是,則以空白補上
10             offset = (channels*width) % 4;
11             if (offset != 0)
12             {
13                 offset = 4 - offset;
14             }
16             for (i = 0; i < height; i++)
17             {
18                 for (j = 0; j < width; j++)
19                 {
20                     for (k = 0; k < 3; k++) //三個通道分別讀取
21                     {
22                         fread(&pixVal, sizeof(unsigned char), 1, pFile);
23                         bmpImg->imageData[(height - 1 - i)*step + j * 3 + k] = pixVal;
24                     }
25                 }
26                 if (offset != 0)
27                 {
28                     for (j = 0; j < offset; j++)
29                     {
30                         fread(&pixVal, sizeof(unsigned char), 1, pFile); //讀空白
31                     }
32                 }
33             }
34         }

3)1bit位圖(黑白圖、二值圖)

 1     if (bmpInfoHeader.biBitCount == 1) {//二值圖
 2             channels = 1;
 3             //一個字節存8個像素,除以8向上取整得到字節數
 4             offset = ((int)ceil(width / 8.0)) % 4; 
 5             //求每行末尾的無意義字節數
 6             if (offset != 0)
 7             {
 8                 offset = 4 - offset;
 9             }
10             bmpImg->channels = 1;
11             //存數據的數據塊,每個像素用一個字節
12             bmpImg->imageData = (unsigned char*)malloc(sizeof(unsigned char)*width*height);
13             step = channels*width; //每行寬度,字節為單位
14              //讀取調色板,二值圖像調色板只有兩種顏色
15              quad = (ClRgbQuad*)malloc(sizeof(ClRgbQuad) * 2); 
16             fread(quad, sizeof(ClRgbQuad), 2, pFile);
17             free(quad);
18             int w = (int)ceil(width / 8.0);//每行占的字節數
19             //用於臨時存儲從每個字節讀取出來的像素值
20             unsigned char bits[8];
21             //計數器,每行達到圖像寬度(width-1),說明后面的bit均為無效,因為最后一個有效字節也只有部分bits是有效的
22             int m = 0;
23             for (i = 0; i < height; i++)
24             {
25                 m = 0;
26                 for (j = 0; j < w; j++)
27                 {
28                     fread(&pixVal, sizeof(unsigned char), 1, pFile);
29                     //獲取字符中沒一位的值,以一個unsigned char[8]的數組存儲
30                     get_bitsofchar(bits, pixVal);
31                     //把每個字節的8位值解析出來,分別存入8個字節
32                     for (int k = 0; k < 8; k++) {
33                         if (m < width) {
34                             //    count[(height - 1 - i)*step + m] = bits[k];
35                             if (bits[k] == 1) { //把值1映射為8位圖中的255
36                                 bits[k] = 255;
37                             }
38                             // 坐標原點在左下角=》(height - 1 - i)*step+j,通過這個變換坐標原點變為左上角
39                             bmpImg->imageData[(height - 1 - i)*step + m] = bits[k];
40                         }
41                         m++;
42                     }
43                 }
44                 if (offset != 0)
45                 {
46                     for (j = 0; j < offset; j++)
47                     {
48                         fread(&pixVal, sizeof(unsigned char), 1, pFile);  //讀取每行的空字節
49                     }
50                 }
51             }
52             for (int i = 0; i < height; i++) {
53                 for (int j = 0; j < width; j++) {
54                     printf("%d ", bmpImg->imageData[i*width + j]);
55                 }
56                 printf("\n");
57             }
58         }

代碼中,為了從字節的8位中解析出8個像素值,定義了位運算函數如下:

 1 //獲取一個字節中pos位的值(0或1)
 2 unsigned char  read_bit(char c, int   pos)
 3 {
 4     char   b_mask = 0x01;
 5     b_mask = b_mask << pos;
 6     //字符c和b_mask做位運算如果還是等於b_mask,說明該位為1 
 7     if ((c&b_mask) == b_mask)
 8         return   1;
 9     else
10         return   0;
11 }
12 
13 //將一個Char的所有bit以單個數值(0、1)的形式解析出來
14 void  get_bitsofchar(unsigned char bits[8], char c)
15 {
16     int k = 0;
17     unsigned char t;
18     for (k = 0; k < 8; k++)
19     {
20         bits[k] = read_bit(c, k);
21     }
22     //逆序排列各個bit位——計算機中順序是從低位向高位排列的
23     for (k = 0; k < 4; k++)
24     {
25         t = bits[k];
26         bits[k] = bits[7 - k];
27         bits[7 - k] = t;
28     }
29 }

六、寫BMP文件

1、寫8bit位圖

 1     //先寫文件類型標識
 2          fileType = 0x4D42;
 3     fwrite(&fileType, sizeof(unsigned short), 1, pFile);
 4      if (bmpImg->channels == 1)//8位,單通道,灰度圖  
 5     {      
 6                 //每行字節數
 7         step = bmpImg->width;
 8                //如果step 不是4字節的整數倍,湊整
 9         offset = step % 4;
10         if (offset != 0)
11         {
12             offset = 4 - offset;
13             step += offset;
14         }
15                 //寫文件頭
16         bmpFileHeader.bfSize = 54 + 256 * 4 + bmpImg->width;
17         bmpFileHeader.bfReserved1 = 0;
18         bmpFileHeader.bfReserved2 = 0;
19         bmpFileHeader.bfOffBits = 54 + 256 * 4;
20         fwrite(&bmpFileHeader, sizeof(ClBitMapFileHeader), 1, pFile);
21                  //寫位圖信息頭
22         bmpInfoHeader.biSize = 40;
23         bmpInfoHeader.biWidth = bmpImg->width;
24         bmpInfoHeader.biHeight = bmpImg->height;
25         bmpInfoHeader.biPlanes = 1;
26         bmpInfoHeader.biBitCount = 8;
27         bmpInfoHeader.biCompression = 0;
28         bmpInfoHeader.biSizeImage = bmpImg->height*step;
29         bmpInfoHeader.biXPelsPerMeter = 0;
30         bmpInfoHeader.biYPelsPerMeter = 0;
31         bmpInfoHeader.biClrUsed = 256;
32         bmpInfoHeader.biClrImportant = 256;
33         fwrite(&bmpInfoHeader, sizeof(ClBitMapInfoHeader), 1, pFile);
34         //寫調色板,是一個灰度值序列,長度255
35         quad = (ClRgbQuad*)malloc(sizeof(ClRgbQuad) * 256);
36         for (i = 0; i < 256; i++)
37         {
38             quad[i].rgbBlue = i;
39             quad[i].rgbGreen = i;
40             quad[i].rgbRed = i;
41             quad[i].rgbReserved = 0;
42         }
43         fwrite(quad, sizeof(ClRgbQuad), 256, pFile);
44         free(quad);
45                //寫每個像素
46         for (i = bmpImg->height - 1; i > -1; i--)
47         {
48             for (j = 0; j < bmpImg->width; j++)
49             {
50                 pixVal = bmpImg->imageData[i*bmpImg->width + j];
51                 fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
52             }
53                         //寫無意義充填字節
54             if (offset != 0)
55             {
56                 for (j = 0; j < offset; j++)
57                 {
58                     pixVal = 0;
59                     fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
60                 }
61             }
62         }
63     }

2、寫24bit位圖

 1       fileType = 0x4D42;
 2     fwrite(&fileType, sizeof(unsigned short), 1, pFile);
 3           //24位,3通道,真彩圖  
 4     if (bmpImg->channels == 3)
 5     {
 6         step = bmpImg->channels*bmpImg->width;
 7         offset = step % 4;
 8         if (offset != 0)
 9         {
10             offset = 4 - offset;
11             step += offset;
12         }
13                 //寫文件頭
14         bmpFileHeader.bfSize = bmpImg->height*step + 54;
15         bmpFileHeader.bfReserved1 = 0;
16         bmpFileHeader.bfReserved2 = 0;
17         bmpFileHeader.bfOffBits = 54;
18         fwrite(&bmpFileHeader, sizeof(ClBitMapFileHeader), 1, pFile);
19                 //寫位圖信息頭
20         bmpInfoHeader.biSize = 40;
21         bmpInfoHeader.biWidth = bmpImg->width;
22         bmpInfoHeader.biHeight = bmpImg->height;
23         bmpInfoHeader.biPlanes = 1;
24         bmpInfoHeader.biBitCount = 24;
25         bmpInfoHeader.biCompression = 0;
26         bmpInfoHeader.biSizeImage = bmpImg->height*step;
27         bmpInfoHeader.biXPelsPerMeter = 0;
28         bmpInfoHeader.biYPelsPerMeter = 0;
29         bmpInfoHeader.biClrUsed = 0;
30         bmpInfoHeader.biClrImportant = 0;
31         fwrite(&bmpInfoHeader, sizeof(ClBitMapInfoHeader), 1, pFile);
32                //寫像素每個像素有3個值(R,G,B)
33         for (i = bmpImg->height - 1; i > -1; i--)
34         {
35             for (j = 0; j < bmpImg->width; j++)
36             {
37                 pixVal = bmpImg->imageData[i*bmpImg->width * 3 + j * 3];
38                 fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
39                 pixVal = bmpImg->imageData[i*bmpImg->width * 3 + j * 3 + 1];
40                 fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
41                 pixVal = bmpImg->imageDat,a[i*bmpImg->width * 3 + j * 3 + 2];
42                 fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
43             }
44             if (offset != 0)
45             {
46                 for (j = 0; j < offset; j++)
47                 {
48                     pixVal = 0;
49                     fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
50                 }
51             }
52         }
53     }

 

參考資料:

【1】百度百科:BMP文件

【2】CSDN博客:用C語言對BMP進行讀寫

【3】CSDN博客:BMP格式介紹

【4】博客園:C語言編寫BMP讀寫程序

源代碼:

bmp.h文件:

 1 #pragma once
 2 #include <stdio.h>  
 3 typedef struct
 4 {
 5   //  unsigned short    bfType;  //不單獨讀就出錯 (2bytes)
 6     unsigned long    bfSize; //文件大小,字節單位表示(4bytes)
 7     unsigned short    bfReserved1;//保留,必須設0(2bytes)
 8     unsigned short    bfReserved2;//保留,必須設0(2bytes)
 9     unsigned long    bfOffBits;//說明從文件頭到實際圖像數據之間的偏移量(4bytes)
10 } ClBitMapFileHeader;
11 
12 //C語言結構體大小計算規則:
13 //1、每個成員的偏移量都必須是當前成員所占內存大小的整數倍如果不是編譯器會在成員之間加上填充字節。
14 //2、當所有成員大小計算完畢后,編譯器判斷當前結構體大小是否是結構體中最寬的成員變量大小的整數倍
15 //如果不是會在最后一個成員后做字節填充。
16 
17 typedef struct
18 {
19     unsigned long  biSize;//說明位圖信息頭結構所需要的字節數(4bytes)
20     long   biWidth;//列出,像素為單位(4bytes)
21     long   biHeight;//行數,像素為單位(4bytes)
22     unsigned short   biPlanes;//為目標設備說明位面數,其值將總是被設為1(2bytes)
23     unsigned short   biBitCount;//說明比特數/象素,其值為1、4、8、16、24、或32,平時用到的圖像絕大部分是24位(真彩色)和8位256色(2bytes)
24     unsigned long  biCompression;//說明圖象數據壓縮的類型,一般沒有沒有壓縮的類型,一般為0  (4bytes) 
25     unsigned long  biSizeImage;//說明圖象的大小,以字節為單位(4bytes) 
26     long   biXPelsPerMeter; //說明水平分辨率,用象素/米表示(4bytes) 
27     long   biYPelsPerMeter;//說明垂直分辨率,用象素/米表示(4bytes) 
28     unsigned long   biClrUsed;//說明位圖實際使用的彩色表中的顏色索引數(設為0的話,則說明使用所有調色板項)。(4bytes) 
29     unsigned long   biClrImportant;//說明對圖象顯示有重要影響的顏色索引的數目,如果是0,表示都重要(4bytes) 
30 } ClBitMapInfoHeader;
31 
32 typedef struct
33 {
34     unsigned char rgbBlue; //該顏色的藍色分量  
35     unsigned char rgbGreen; //該顏色的綠色分量  
36     unsigned char rgbRed; //該顏色的紅色分量  
37     unsigned char rgbReserved; //保留值  
38 } ClRgbQuad;
39 
40 typedef struct
41 {
42     int width;  //寬度,像素單位
43     int height;//高度,像素單位
44     int channels;//通道數
45     unsigned char* imageData;//圖像數據
46 }ClImage;
47 
48 //讀取文件
49 ClImage* clLoadImage(char* path); 
50 //保存文件
51 bool clSaveImage(char* path, ClImage* bmpImg);
bmp.h 

注:根據C語言中結構體大小的計算規則(參考:C語言結構體大小計算),結構體變量的Size並不一定是其所有字段size之和,導致如果直接用一個結構體變量作為讀入數據塊的容器,可能產生偏差。因此,對文件頭的結構體定義去掉了字段bfType,而是在讀文件的程序中單獨用一個變量讀取該信息。

bmp.cpp文件:

  1 #include "bmp.h"  
  2 #include <stdio.h>  
  3 #include <stdlib.h>  
  4 #include <math.h>
  5 
  6 //獲取一個字節中pos位的值(0或1)
  7 unsigned char  read_bit(char c, int   pos)
  8 {
  9     char   b_mask = 0x01;
 10     b_mask = b_mask << pos;
 11     //字符c和b_mask做位運算如果還是等於b_mask,說明該位為1 
 12     if ((c&b_mask) == b_mask)
 13         return   1;
 14     else
 15         return   0;
 16 }
 17 
 18 //將一個Char的所有bit以單個數值(0、1)的形式解析出來
 19 void  get_bitsofchar(unsigned char bits[8], char c)
 20 {
 21     int k = 0;
 22     unsigned char t;
 23     for (k = 0; k < 8; k++)
 24     {
 25         bits[k] = read_bit(c, k);
 26     }
 27     //逆序排列各個bit位——計算機中順序是從低位向高位排列的
 28     for (k = 0; k < 4; k++)
 29     {
 30         t = bits[k];
 31         bits[k] = bits[7 - k];
 32         bits[7 - k] = t;
 33     }
 34 }
 35 /*
 36 讀取BMP文件,返回一個ClImage對象
 37 */
 38 ClImage* clLoadImage(char* path)
 39 {
 40     ClImage* bmpImg;
 41     FILE* pFile;
 42     unsigned short fileType;
 43     ClBitMapFileHeader bmpFileHeader;
 44     ClBitMapInfoHeader bmpInfoHeader;
 45     int channels = 1;
 46     int width = 0;
 47     int height = 0;
 48     int step = 0;
 49     int offset = 0;
 50     unsigned char pixVal;
 51     ClRgbQuad* quad;
 52     int i, j, k;
 53 
 54     bmpImg = (ClImage*)malloc(sizeof(ClImage));
 55     //pFile = fopen(path, "rb");
 56     fopen_s(&pFile, path, "rb");
 57     if (!pFile)
 58     {
 59         free(bmpImg);
 60         return NULL;
 61     }
 62     //如果不先讀取bifType,根據C語言結構體Sizeof運算規則——整體大於部分之和,從而導致讀文件錯位
 63     fread(&fileType, sizeof(unsigned short), 1, pFile);
 64     if (fileType == 0x4D42)
 65     {
 66         printf("文件類型標識正確! \n");
 67         //讀文件頭(12bytes,除去bifType)
 68         fread(&bmpFileHeader, sizeof(ClBitMapFileHeader), 1, pFile);
 69         printf("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n");
 70         printf("bmp文件頭信息:\n");
 71         printf("文件大小:%d \n", bmpFileHeader.bfSize);
 72         printf("保留字:%d \n", bmpFileHeader.bfReserved1);
 73         printf("保留字:%d \n", bmpFileHeader.bfReserved2);
 74         printf("位圖數據偏移字節數:%d \n", bmpFileHeader.bfOffBits);
 75         //讀位圖信息頭40bytes
 76         fread(&bmpInfoHeader, sizeof(ClBitMapInfoHeader), 1, pFile);
 77         printf("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n");
 78         printf("bmp文件信息頭\n");
 79         printf("結構體長度:%d \n", bmpInfoHeader.biSize);
 80         printf("位圖寬度:%d \n", bmpInfoHeader.biWidth);
 81         printf("位圖高度:%d \n", bmpInfoHeader.biHeight);
 82         printf("位圖平面數:%d \n", bmpInfoHeader.biPlanes);
 83         printf("顏色位數:%d \n", bmpInfoHeader.biBitCount);
 84         printf("壓縮方式:%d \n", bmpInfoHeader.biCompression);
 85         printf("實際位圖數據占用的字節數:%d \n", bmpInfoHeader.biSizeImage);
 86         printf("X方向分辨率:%d \n", bmpInfoHeader.biXPelsPerMeter);
 87         printf("Y方向分辨率:%d \n", bmpInfoHeader.biYPelsPerMeter);
 88         printf("使用的顏色數:%d \n", bmpInfoHeader.biClrUsed);
 89         printf("重要顏色數:%d \n", bmpInfoHeader.biClrImportant);
 90         printf("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n");
 91 
 92         //獲取圖像寬度,高度
 93         width = bmpInfoHeader.biWidth;
 94         height = bmpInfoHeader.biHeight;
 95         bmpImg->width = width;
 96         bmpImg->height = height;
 97 
 98         if (bmpInfoHeader.biBitCount == 1) {//二值圖
 99             printf("該文件有調色板,即該位圖二值圖\n\n");
100             channels = 1;
101             offset = ((int)ceil(width / 8.0)) % 4;  //一個字節存8個像素
102 
103             //求每行末尾的空字節數
104             if (offset != 0)
105             {
106                 offset = 4 - offset;
107             }
108             bmpImg->channels = 1;
109             bmpImg->imageData = (unsigned char*)malloc(sizeof(unsigned char)*width*height);
110             step = channels*width; //每行寬度
111 
112             quad = (ClRgbQuad*)malloc(sizeof(ClRgbQuad) * 2); //讀取調色板
113             fread(quad, sizeof(ClRgbQuad), 2, pFile);
114             free(quad);
115 
116 
117             int w = (int)ceil(width / 8.0);//每行占的字節數
118             unsigned char bits[8];
119             int m = 0;
120             for (i = 0; i < height; i++)
121             {
122                 m = 0;
123                 for (j = 0; j < w; j++)
124                 {
125                     fread(&pixVal, sizeof(unsigned char), 1, pFile);
126                     //獲取字符中沒一位的值,以一個unsigned char[8]的數組存儲
127                     get_bitsofchar(bits, pixVal);
128                     //把每個字節的8位值解析出來,分別存入8個字節
129                     for (int k = 0; k < 8; k++) {
130                         if (m < width) {
131                             //    count[(height - 1 - i)*step + m] = bits[k];
132                             if (bits[k] == 1) { //把值1映射為8位圖中的255
133                                 bits[k] = 255;
134                             }
135                             // 坐標原點在左下角=》(height - 1 - i)*step+j,通過這個變換坐標原點變為左上角
136                             bmpImg->imageData[(height - 1 - i)*step + m] = bits[k];
137                         }
138                         m++;
139                     }
140                 }
141                 if (offset != 0)
142                 {
143                     for (j = 0; j < offset; j++)
144                     {
145                         fread(&pixVal, sizeof(unsigned char), 1, pFile);  //讀取每行的空字節
146                     }
147                 }
148             }
149             for (int i = 0; i < height; i++) {
150                 for (int j = 0; j < width; j++) {
151                     printf("%d ", bmpImg->imageData[i*width + j]);
152                 }
153                 printf("\n");
154             }
155         }
156 
157 
158         else if (bmpInfoHeader.biBitCount == 4) {//十六色位圖
159             //一般不用這種格式
160         }
161         else if (bmpInfoHeader.biBitCount == 8)//256色位圖
162         {
163             printf("該文件有調色板,即該位圖為非真彩色8位256色位圖\n\n");
164             channels = 1;
165             offset = (channels*width) % 4;
166             if (offset != 0)
167             {
168                 offset = 4 - offset;
169             }
170             bmpImg->channels = 1;
171             bmpImg->imageData = (unsigned char*)malloc(sizeof(unsigned char)*width*height);
172             step = channels*width; //每行寬度
173 
174             quad = (ClRgbQuad*)malloc(sizeof(ClRgbQuad) * 256); //讀取調色板
175             fread(quad, sizeof(ClRgbQuad), 256, pFile);
176             free(quad);
177 
178             for (i = 0; i < height; i++)
179             {
180                 for (j = 0; j < width; j++)
181                 {
182                     fread(&pixVal, sizeof(unsigned char), 1, pFile);
183                     // 坐標原點在左下角=》(height - 1 - i)*step+j,通過這個變換坐標原點變為左上角
184                     bmpImg->imageData[(height - 1 - i)*step + j] = pixVal;
185                 }
186                 if (offset != 0)
187                 {
188                     for (j = 0; j < offset; j++)
189                     {
190                         fread(&pixVal, sizeof(unsigned char), 1, pFile);  //讀取每行的空字節
191                     }
192                 }
193             }
194         }
195         //16位高彩色圖
196         else if (bmpInfoHeader.biBitCount == 16) {
197             //一般不用這種格式
198         }
199         //真彩色
200         else if (bmpInfoHeader.biBitCount == 24)
201         {
202             printf("該位圖為位真彩色\n\n");
203             channels = 3;
204             bmpImg->channels = 3;  //每個像元占3列
205             bmpImg->imageData = (unsigned char*)malloc(sizeof(unsigned char)*width * 3 * height);
206             step = channels*width;
207             offset = (channels*width) % 4;
208             if (offset != 0)
209             {
210                 offset = 4 - offset; //計算空白數據,因為每行的長度只能是4的整數倍,如果不是,則以空白補上
211             }
212 
213             for (i = 0; i < height; i++)
214             {
215                 for (j = 0; j < width; j++)
216                 {
217                     for (k = 0; k < 3; k++) //三個通道分別讀取
218                     {
219                         fread(&pixVal, sizeof(unsigned char), 1, pFile);
220                         bmpImg->imageData[(height - 1 - i)*step + j * 3 + k] = pixVal;
221                     }
222                     //kzSetMat(bmpImg->mat, height-1-i, j, kzScalar(pixVal[0], pixVal[1], pixVal[2]));  
223                 }
224                 if (offset != 0)
225                 {
226                     for (j = 0; j < offset; j++)
227                     {
228                         fread(&pixVal, sizeof(unsigned char), 1, pFile); //讀空白
229                     }
230                 }
231             }
232         }
233         else if (bmpInfoHeader.biBitCount == 32) { //32位位圖--具有透明透明通道
234             //一般不用這種格式
235         }
236     }
237     return bmpImg;
238 }
239 
240 bool clSaveImage(char* path, ClImage* bmpImg)
241 {
242     FILE *pFile;
243     unsigned short fileType;
244     ClBitMapFileHeader bmpFileHeader;
245     ClBitMapInfoHeader bmpInfoHeader;
246     int step;
247     int offset;
248     unsigned char pixVal = '\0';
249     int i, j;
250     ClRgbQuad* quad;
251 
252     //pFile = fopen(path, "wb");
253     fopen_s(&pFile, path, "wb");
254     if (!pFile)
255     {
256         return false;
257     }
258 //
259        fileType = 0x4D42;
260     fwrite(&fileType, sizeof(unsigned short), 1, pFile);
261 
262 
263     if (bmpImg->channels == 3)//24位,通道,彩圖  
264     {
265         step = bmpImg->channels*bmpImg->width;
266         offset = step % 4;
267         if (offset != 0)
268         {
269             offset = 4 - offset;
270             step += offset;
271         }
272         /*一個BUG
273         if (offset != 4) {
274             step += 4 - offset;
275         }*/
276         //bmpFileHeader.bfType = 0x4D42;
277         bmpFileHeader.bfSize = bmpImg->height*step + 54;
278         bmpFileHeader.bfReserved1 = 0;
279         bmpFileHeader.bfReserved2 = 0;
280         bmpFileHeader.bfOffBits = 54;
281         fwrite(&bmpFileHeader, sizeof(ClBitMapFileHeader), 1, pFile);
282 
283         bmpInfoHeader.biSize = 40;
284         bmpInfoHeader.biWidth = bmpImg->width;
285         bmpInfoHeader.biHeight = bmpImg->height;
286         bmpInfoHeader.biPlanes = 1;
287         bmpInfoHeader.biBitCount = 24;
288         bmpInfoHeader.biCompression = 0;
289         bmpInfoHeader.biSizeImage = bmpImg->height*step;
290         bmpInfoHeader.biXPelsPerMeter = 0;
291         bmpInfoHeader.biYPelsPerMeter = 0;
292         bmpInfoHeader.biClrUsed = 0;
293         bmpInfoHeader.biClrImportant = 0;
294         fwrite(&bmpInfoHeader, sizeof(ClBitMapInfoHeader), 1, pFile);
295 
296         for (i = bmpImg->height - 1; i > -1; i--)
297         {
298             for (j = 0; j < bmpImg->width; j++)
299             {
300                 pixVal = bmpImg->imageData[i*bmpImg->width * 3 + j * 3];
301                 fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
302                 pixVal = bmpImg->imageData[i*bmpImg->width * 3 + j * 3 + 1];
303                 fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
304                 pixVal = bmpImg->imageData[i*bmpImg->width * 3 + j * 3 + 2];
305                 fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
306             }
307             if (offset != 0)
308             {
309                 for (j = 0; j < offset; j++)
310                 {
311                     pixVal = 0;
312                     fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
313                 }
314             }
315         }
316     }
317 
318     else if (bmpImg->channels == 1)//8位,單通道,灰度圖  
319     {
320         step = bmpImg->width;
321         offset = step % 4;
322         if (offset != 0)
323         {
324             offset = 4 - offset;
325             step += offset;
326         }
327         //bmpFileHeader.bfType = 0x4D42;
328         bmpFileHeader.bfSize = 54 + 256 * 4 + bmpImg->width;
329         bmpFileHeader.bfReserved1 = 0;
330         bmpFileHeader.bfReserved2 = 0;
331         bmpFileHeader.bfOffBits = 54 + 256 * 4;
332         fwrite(&bmpFileHeader, sizeof(ClBitMapFileHeader), 1, pFile);
333 
334         bmpInfoHeader.biSize = 40;
335         bmpInfoHeader.biWidth = bmpImg->width;
336         bmpInfoHeader.biHeight = bmpImg->height;
337         bmpInfoHeader.biPlanes = 1;
338         bmpInfoHeader.biBitCount = 8;
339         bmpInfoHeader.biCompression = 0;
340         bmpInfoHeader.biSizeImage = bmpImg->height*step;
341         bmpInfoHeader.biXPelsPerMeter = 0;
342         bmpInfoHeader.biYPelsPerMeter = 0;
343         bmpInfoHeader.biClrUsed = 256;
344         bmpInfoHeader.biClrImportant = 256;
345         fwrite(&bmpInfoHeader, sizeof(ClBitMapInfoHeader), 1, pFile);
346 
347         //調色板是一個灰度圖
348         quad = (ClRgbQuad*)malloc(sizeof(ClRgbQuad) * 256);
349         for (i = 0; i < 256; i++)
350         {
351             quad[i].rgbBlue = i;
352             quad[i].rgbGreen = i;
353             quad[i].rgbRed = i;
354             quad[i].rgbReserved = 0;
355         }
356         fwrite(quad, sizeof(ClRgbQuad), 256, pFile);
357         free(quad);
358 
359         for (i = bmpImg->height - 1; i > -1; i--)
360         {
361             for (j = 0; j < bmpImg->width; j++)
362             {
363                 pixVal = bmpImg->imageData[i*bmpImg->width + j];
364                 fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
365             }
366             if (offset != 0)
367             {
368                 for (j = 0; j < offset; j++)
369                 {
370                     pixVal = 0;
371                     fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
372                 }
373             }
374         }
375     }
376     fclose(pFile);
377 
378     return true;
379 }
bmp.cpp

 

主程序文件:

 1 #include "bmp.h"  
 2 #include <stdio.h>
 3 
 4 void main()
 5 {
 6     ClImage* img = clLoadImage("c:/122.bmp");
 7     bool flag = clSaveImage("c:/result.bmp", img);
 8     if (flag)
 9     {
10         printf("save ok... \n");
11     }
12 }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM