一、簡介
BMP文件格式,是Windows系統中廣泛使用的圖像文件格式。由於它可以不作任何變換地保存圖像像素域的數據,因此成為我們取得原始數據的重要來源。Windows的圖形用戶界面(graphical user interfaces)也在它的內建圖像子系統GDI中對BMP格式提供了支持。
BMP文件的數據按照從文件頭開始的先后順序分為四個部分:
- bmp文件頭(bmp file header):提供文件的格式、大小等信息
- 位圖信息頭(bitmap information):提供圖像數據的尺寸、位平面數、壓縮方式、顏色索引等信息
- 調色板(color palette):可選,如使用索引來表示圖像,調色板就是索引與其對應的顏色的映射表
- 位圖數據(bitmap data):就是圖像數據啦
二、文件各個部分詳細描述(以字節文單位):
typedef struct tagBITMAPFILEHEADER
{
WORDbfType; // 位圖文件的類型,必須為BM
DWORD bfSize; // 位圖文件的大小,以字節為單位
WORDbfReserved1; // 位圖文件保留字,必須為0
WORDbfReserved2; // 位圖文件保留字,必須為0
DWORD bfOffBits; // 位圖數據的起始位置,以相對於位圖
// 文件頭的偏移量表示,以字節為單位
} BITMAPFILEHEADER;
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;
- 1字節用於藍色分量
- 1字節用於綠色分量
- 1字節用於紅色分量
- 1字節用於填充符(設置為0,透明度)
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;
當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);
注:根據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 }
主程序文件:
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 }