用c、cpp实现位图(BMP)读入写出的思路及细节
部分资料来自于:
https://blog.csdn.net/zhangquan2015/article/details/80160864
https://blog.csdn.net/weixin_42164528/article/details/80594667
BMP介绍
BMP(全称Bitmap)是Windows操作系统中的标准图像文件格式,可以分成两类:设备相关位图(DDB)和设备无关位图(DIB)
BMP文件所占用的空间很大
图像深度可选1bit、4bit、8bit及24bit
注意,BMP文件存储数据时,
图像的扫描方式是按 从左到右、从下到上 的顺序。
由于BMP文件格式是Windows环境中交换与图有关的数据的一种标准,
因此在Windows环境中运行的图形图像软件都支持BMP图像格式。
通道的概念
图像的通道指的是什么?
基本上,描述一个像素点,如果是灰度,那么只需要一个数值来描述它,就是单通道。
如果一个像素点,有RGB三种颜色(红绿蓝)来描述它,就是三通道。
当然也有四通道的说法,R、G、B加上一个A通道,表示透明度
图像深度
深度,基于通道数,表示存储每个像素所用的位数
最常见的就是24位图,所谓的24位图,就是说一个像素的颜色信息用24位来表示,也就是说,对于三原色BRG,每一个颜色都用以字节(8)位来表示。
除了24位图,还有1位(单色),2位(4色,CGA),4位(16色,VGA),8位(256色),16位(增强色),24位(真彩色)和32位等
而32位正是上面所说的四通道,即增加了Alpha值(透明度)的
BMP中数据存储方式
先解释下数据存贮方式:
(1)在windows中,颜色顺序是:B G R
(2)BMP的内存行顺序和图像显示的行顺序是上下颠倒的。
(这就是前面为什么要强调扫描顺序是从下到上。这句话通俗地讲,BMP内存的第0行,是真实图像下面的最后一行)
举个栗子,假如图像为 2*2 大小,像素三颜色按照RGB的顺序, 我们看到的图像为:
1 2 3, 11 22 33
4 5 6, 44 55 66
但是在内存中,表示如下:
6 5 4, 66 55 44 (0 0) -- 第0行
3 2 1, 33 22 11 (0 0) -- 第1行
注意,通常内存是需要内存对齐的,所以每行后面可能会有对齐所产生的0.
BMP文件的数据
BMP文件的数据按照从文件头开始、按内存的先后顺序分为以下四个部分:
文件头、信息头、调色板和数据域,
我们读入BMP的时候,类的格式按照这四块写好即可
(1)**header 文件头 共14字节 **
提供文件的格式、大小等信息
(2)infoheader 信息头 共40字节
提供图像数据的尺寸、位平面数、压缩方式、颜色索引等信息
(3)optional palette 可选调色板(24位的不需要,可选)
如使用索引来表示图像,调色板就是索引与其对应的颜色的映射表
(4)**image data 数据域 **
图像像素信息的数据,包括:高度,宽度,图像通道数,像素信息等
(1)文件头 header 14字节
文件头包含如下信息:
bfType:2字节,文件类型;
bfSize:4字节,文件大小;
bfReserved1:2字节,保留,必须设置为0;
bfReserved2:2字节,保留,必须设置为0;
bfOffBits:4字节,从头到位图数据的偏移;
下面同理
(2)位图信息头 bitmap information 40字节
biSize:4字节,信息头的大小,即40;
biWidth:4字节,以像素为单位说明图像的宽度;
biHeight:4字节,以像素为单位说明图像的高度,(它可能是负数)
biPlanes:2字节,为目标设备说明颜色平面数,总被设置为1;
biBitCount:2字节,说明比特数/像素数,值有1、2、4、8、16、24、32;
注意这个biBitCount就是指他的每个像素用8多少位表示,即 图像深度
biCompression:4字节,说明图像的压缩类型,最常用的就是0(BI_RGB),表示不压缩;
biSizeImages:4字节,说明位图数据的大小,当用BI_RGB格式时,可以设置为0;
biXPelsPerMeter:表示水平分辨率,单位是像素/米,有符号整数;
biYPelsPerMeter:表示垂直分辨率,单位是像素/米,有符号整数;
biClrUsed:说明位图使用的调色板中的颜色索引数,为0说明使用所有;
biClrImportant:说明对图像显示有重要影响的颜色索引数,为0说明都重要;
(3)调色板 optional palette
注意:有的图像没有调色板,比如24、32位色图
调色板是可选的,不过这里讲的是8位色图有调色板
调色板就是一个颜色的索引,8位色图,一共有256种颜色,由于每个颜色都有RGB三原色,也就是要3个字节表示,这样的话256个颜色就不能表示所有的颜色。
所以就需要一个索引,用一个字节的索引指向4个字节表示的颜色(RGB加上Alpha值)。如果把这4个字节表示为一个Color类型,那么调色板就是Color的数组
由于Color类型也是一个数组,调色板就像一个二维数组palette[N][4]
其中N是颜色的数量,这里就是256。因此,这个例子中的调色板的大小就是256x4=1024字节,
(4)位图数据域 image data
最为重要的一块
注意区分情况
1)8位色图
由于是8位色图,所以每个像素用1个字节表示,取出每个字节,显示到相应的设备上就可以了。
注意,这里的biHeight为正数,说明图像倒立,从左下角开始到右上角,以行为主序排列。
2)24位色图
按照BGR的顺序排列,即三色
3)32位色图
按照BGRAlpha排列,即三色+透明度
一般就是这上面三种情况,下面要介绍对齐规则
注意点
内存对齐
如果不懂什么是内存对齐,请看本人的另外的一篇BLOG:
https://www.cnblogs.com/et3-tsy/p/12918932.html
这基于这一点,我们本来应该是14字节的结构体很容易就成了16字节
对此,我们定义结构体的时候有两种处理方式:
1)#pragma pack(1)
取消编译器的字节对齐,从而让结构体的实际内存成为理想中的14字节
2)单独读文件类型
把文件类型单独读入,这样做即可以巧妙回避内存对齐这一问题,又可以方便校验文件,从而防止异常读入
BMP生成规则
Windows默认的扫描的最小单位是4字节,BMP图像顺应了这个要求
也就是说,每行的数据内存必须要是4字节的倍数,BMP才能正常显示
所以,
读入时-->我们要忽视它用于填充的0
写出时-->我们要补上它用于填充的0,使得每行的内存是4字节的倍数