一、简介
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 }