该内容主要涉及位图文件结构以及位图存储时的四字节对齐两个重点。两个重点之后给出BYTE数组保存为8位位图的c++ code。
如想直接查阅代码可以跳过1、2部分,直接查看第3部分。
本文主要参照如下五个博客,将内容综合整理以备查阅。
1)位图格式分析:https://blog.csdn.net/aidem_brown/article/details/80500637
https://blog.csdn.net/qq_31094099/article/details/80286126
https://www.cnblogs.com/lzlsky/archive/2012/08/16/2641698.html
2)位图文件头BITMAPFILEHEADER中一些参数的分析:
https://blog.csdn.net/q408384053/article/details/7037593
3)c++读取以及保存8位位图的代码
https://blog.csdn.net/yesejiangnan/article/details/46005291
1.位图文件结构
BMP文件格式,又称为Bitmap(位图)或是DIB(Device-Independent Device,设备无关位图),是Windows系统中广泛使用的图像文件格式。由于它可以不作任何变换地保存图像像素域的数据,因此成为我们取得RAW数据的重要来源。
BMP文件的数据按照从文件头开始的先后顺序分为四个部分:
1)bmp文件头(bmp file header):提供文件的格式、大小等信息
2)位图信息头(bitmap information):提供图像数据的尺寸、位平面数、压缩方式、颜色索引等信息
3)调色板(color palette):可选,如使用索引来表示图像,调色板就是索引与其对应的颜色的映射表
4)位图数据(bitmap data):就是图像数据,位图全部的像素,是按照自下向上,自左向右的顺序排列的。
具体如下:
1)bmp文件头:
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;
bfType:该值必须是0x4d42,也就是((WORD)('M' << 8) | 'B')
bfSize:说明文件的大小,用字节为单位。其值为bfoffBIts加上数据区的大小
bfReserved1/bfReserved2:必须设置为0
bfOffBits:说明文件头开始到实际的图像数据之间的字节的偏移量。这个参数非常有用,因为位图信息头和调色板的长度会根据 不同情况而变化,所以你可以用这个偏移值迅速的从文件中读取到位数据。
bfOffBits = sizeof (BITMAPFILEHEADER) + sizeof (BITMAPINFOHEADER) + NumberOfRGBQUAD * sizeof (RGBQUAD)
2)位图信息头
typedef struct tagBITMAPINFOHEADER{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER;
biSize:说明BITMAPINFOHEADER结构所需要的字节数,biSize=sizeof(BITMAPINFOHEADER)或者biSize=40(信息头有40个 字节)
biWidth:说明图像的宽度,以像素为单位
biPlanes:为目标设备说明位面数,其值总是被设为1
biBitCount:说明比特数像素,其值为1、4、8、16、24、32
biCompression:说明图像数据的压缩类型,0 :BI_RGB 不压缩(最常用);1:BI_RLE8 8比特游程编码(BLE),只用于8位 位图,2 :BI_RLE4 4比特游程编码(BLE),只用于4位位图;3 :BI_BITFIELDS比特域(BLE),只用于 16/32位位图
biSizeImage:说明图像的大小,以字节为单位。当用BI_RGB格式时,总设置为0
biXPelsPerMeter:说明水平分辨率,用像素/米表示,有符号整数,看代码常设置为0(原因不清楚,有清楚的大大还请不吝赐教^_^)
biYPelsPerMeter:说明垂直分辨率,用像素/米表示,有符号整数,这个也常设置为0(原因同上不清楚)
biClrUsed:说明位图实际使用的调色板索引数,0:使用所有的调色板索引
biClrImportant:说明对图像显示有重要影响的颜色索引的数目,如果是0,表示都重要。
3)调色板(color palette)
彩色表/调色板(color table)是单色、16色和256色图像文件所特有的,相对应的调色板大小是2、16和256,调色板以4字节为单位,每4个字节存放一个颜色值,图像 的数据是指向调色板的索引。
可以将调色板想象成一个数组,每个数组元素的大小为4字节,假设有一256色的BMP图像的调色板数据为:
调色板[0]=黑、调色板[1]=白、调色板[2]=红、调色板[3]=蓝…调色板[255]=黄
图像数据01 00 02 FF表示调用调色板[1]、调色板[0]、调色板[2]和调色板[255]中的数据来显示图像颜色。
在早期的计算机中,显卡相对比较落后,不一定能保证显示所有颜色,所以在调色板中的颜色数据应尽可能将图像中主要的颜色按顺序排列在前面,位图信息 头的biClrImportant字段指出了有多少种颜色是重要的。
每个调色板的大小为4字节,按蓝、绿、红存储一个颜色值。
typedef struct tagRGBQUAD {
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD;
rgbBlue:蓝色值
rgbGreen: 绿色值
rgbRed: 红色值
rgbReserved:保留,总为0
4)位图数据
如果图像是单色、16色和256色,则紧跟着调色板的是位图数据,位图数据是指向调色板的索引序号。
如果位图是16位、24位和32位色,则图像文件中不保留调色板,即不存在调色板,图像的颜色直接在位图数据中给出,
此时位图数据区的每个像素的RGB颜色阵列排布:24位RGB按照BGR的顺序来存储每个像素的各颜色通道的值,一个像素的所有颜色分量值都存完后才存下一个下一个像素,不进行交织存储;32位数据按照BGRA的顺序存储,其余与24位位图的方式一样
2.四字节对齐
要求每行的数据的长度必须是4的倍数,如果不够需要进行比特填充(以0填充),这样可以达到按行的快速存取。这时,位图数据区的大小就未必是 图片宽×每像素字节数×图片高 能表示的了,因为每行可能还需要进行比特填充。若不能被4整除,则在该位图每一行的十六进制码末尾“补”1至3个字节的“00”。
举例说明(回归篇名对8位位图进行计算):
8位(bit)位图:彩色版中有2^8=256种颜色,刚好可用两位十六进制码(16^2=256)表示,占1字节。
一幅宽511*高512的8位位图,每一行像素所占字节=511*1=511个字节,检验其被4除余1,则在每行十六进制末尾加一个字节补“00”,变为512个字节。因此我们计算该图大小时应先判断是否“补零”,再得出算法:
位图文件头(14字节00000000h开始到0000000Dh)+位图信息头(40字节0000000Eh开始到00000035h)+调色板(256×彩色表4字节 00000036h开始到00000435h)+实际像素点占内存(高512×每行512字节)=263 222字节(Byte)。
同时需要注意:补零”只针对位图的宽进行检验,如果刚才的图像宽和高颠倒,变为宽512*高511,则实际像素点占内存高511*每行512个字节。(还可与本文开头链接1末尾的例子对比分析,更方便理解)
3.BYTE数组保存为8位BMP/bitmap位图
有了上边两个重点内容的介绍,理解下面保存位图的代码就非常轻松了。
此处代码主要来源于本篇开头第5个链接,此处对其中的内容增加了注释,方便理解。
typedef unsigned char BYTE;
//八位图像保存 将BYTE数据保存成为图像,图像路径是filename
bool RmwWriteByteImg2BmpFile(BYTE *pImg, int width, int height, const char * filename)
{
FILE * BinFile;
BITMAPFILEHEADER FileHeader; //定义BMP文件头
BITMAPINFOHEADER BmpHeader; //定义信息头
int i, extend;
bool Suc = true;
BYTE p[4], *pCur;
BYTE* ex;
//存储图像数据,每行字节数为4的倍数
//所以 + 3是怕出现不满足4的倍数这种情况;如果是4的倍数则结果和不 + 3的结果是一样的;如果不是4的倍数则结果进1位
// /4*4除以四在乘以四是把数据归为4的倍数。
extend = (width + 3) / 4 * 4 - width;
// Open File
if ((BinFile = fopen(filename, "w+b")) == NULL) { return false; }
//参数填法见结构链接 BMP文件头
//FileHeader.bfType = ((WORD)('M' << 8) | 'B');
FileHeader.bfType = 0x4d42;//两种方法都可以
//biBitCount=8时,为256色图像,BMP位图中有256个数据结构RGBQUAD,一个调色板占用4字节数据,所以256色图像的调色板长度为256*4为1024字节。
FileHeader.bfOffBits = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+256 * 4L;//2个头结构后加调色板(bfOffBits = sizeof (BITMAPFILEHEADER) + sizeof (BITMAPINFOHEADER) + NumberOfRGBQUAD * sizeof (RGBQUAD) ;)
FileHeader.bfSize = FileHeader.bfOffBits + (width + extend)*height;//bfSize=bfoffBits+数据区的大小
FileHeader.bfReserved1 = 0;
FileHeader.bfReserved2 = 0;
if (fwrite((void *)&FileHeader, 1, sizeof(FileHeader), BinFile) != sizeof(FileHeader)) Suc = false;
// Fill the ImgHeader 信息头
//BmpHeader.biSize = 40;
BmpHeader.biSize = sizeof(BITMAPINFOHEADER);//sizeof(BITMAPINFOHEADER)=40
BmpHeader.biWidth = width;
BmpHeader.biHeight = height;
BmpHeader.biPlanes = 1;
BmpHeader.biBitCount = 8;
BmpHeader.biCompression = 0;
BmpHeader.biSizeImage = 0;
BmpHeader.biXPelsPerMeter = 0;
BmpHeader.biYPelsPerMeter = 0;
BmpHeader.biClrUsed = 0;
BmpHeader.biClrImportant = 0;
if (fwrite((void *)&BmpHeader, 1, sizeof(BmpHeader), BinFile) != sizeof(BmpHeader)) Suc = false;
// 写入调色板
for (i = 0, p[3] = 0; i<256; i++)
{
//下面两句选择保存的图像颜色刚好互补
//p[0] = p[1] = p[2] = 255 - i; // blue,green,red;
p[0] = p[1] = p[2] = i;
if (fwrite((void *)p, 1, 4, BinFile) != 4) { Suc = false; break; }
}
if (extend)
{
ex = new BYTE[extend]; //填充数组大小为 0~3
memset(ex, 0, extend);
}
//write data 图像数据 从下到上保存
for (pCur = pImg + (height - 1)*width; pCur >= pImg; pCur -= width)
{
if (fwrite((void *)pCur, 1, width, BinFile) != (unsigned int)width) Suc = false; // 真实的数据
if (extend) // 扩充的数据 这里填充0
if (fwrite((void *)ex, 1, extend, BinFile) != 1) Suc = false;
}
// return;
fclose(BinFile);
if (extend)
delete[] ex;
return Suc;
}
使用以上函数即可将BYTE数据保存为8位位图。
文中若有错误以及不妥的地方,还望指出,以便共同学习。