以 bmp 格式图片为例的 c 读入和操作文件

C语言读取和存储bmp格式图片_c 保存bmp-CSDN博客

用Visual Studio查看图片的二进制流-CSDN博客

利用C语言读取BMP文件 - 知乎 (zhihu.com)

bmp 格式

bmp 就是 bitmap。bmp 格式是 Windows 中的标准图像文件格式,能够被多种 Windows 应用所支持。这种格式的特点是包含的图像信息较丰富,几乎不进行压缩,但因此占用磁盘空间较大。

在 mspaint 保存时就可以选择保存为单色位图,16 色位图,256 色位图以及 24 位位图。

bmp 包含四个数据块

bmp 文件头(14byte)

这是一个结构体,含有若干信息。

这部分是识别信息,应用程序会首先普通读取这部分数据,以确保的确是位图文件且没有损坏。

typedef struct{
	uint16_t type;//位图文件的类型,必须为 BM
	uint32_t size;//位图文件的大小,以字节为单位(低位在前)
	uint16_t reserved1;//位图文件保留字,必须为 0
	uint16_t reserved2;//位图文件保留字,必须为 0
	uint32_t off_bits;//位图文件的地址偏移,即起始位置,以相对于位图(低位在前)
}__attribute__ ((packed)) bmp_file_header_t;

位图信息头(40byte)

这部分告诉应用程序图像的详细信息,在屏幕上显示图像会用到这些信息。

typedef struct{
	uint32_t size; //定义该结构体的大小,即为 40
	int32_t width; //位图宽度,以像素为单位
	int32_t height; //位图高度,以像素为单位
	uint16_t planes; //保存所用彩色位面的个数,不常使用
	uint16_t bit_count; //保存每个像素的位数,它是图像的颜色深度
	uint32_t compression; //定义所用的压缩算法
	uint32_t size_image; //位图的大小,以字节为单位。不同于文件大小,这是原始位图数据的大小
	uint32_t x_pels_permeter;  //位图水平分辨率,每米像素数
	uint32_t y_pels_permeter; //位图垂直分辨率,每米像素数
	uint32_t clr_used; //位图实际使用的颜色表中的颜色数
	uint32_t clr_improtant; //位图显示过程中重要的颜色数
} bmp_info_header_t;

  • bit_count 的常用值有 1(双色灰阶),4(16色灰阶),8(256色灰阶)和 24(彩色)。
  • compression 的值有 0(没有压缩,BI_RGB),1(行程长度编码8位 / 像素,BI_RLE8),2(行程长度编码4位 / 像素,BI_RLE4),3(Bit field,BI_BITFIELDS),4(JPEG,BI_JPEG),5(PNG,BI_PNG)
  • size_image 包含为补齐每行字节数是4倍数而添加的空字节

颜色表

颜色表保存所用颜色的定义。

typedef struct _tagRGBQUAR{
	BYTE rgbBlue; //指定蓝色强度
	BYTE rgbGreen; //指定绿色强度
	BYTE rgbRed; //指定红色强度
	BYTE rgbReserved; //保留,设为 0
} RGBQUAD;

1、4、8 位图像才会用到彩色表,16、24、32 位图像不需要。
bit_count 为 1 时是 2 色图像,bmp 中有 2 个数据结构 RGBQUAD,一个占 4 字节,故 2 色图像的颜色表长度为 8 字节。
bit_count 为 4 时是 16 色图像,bmp 中有 16 个数据结构 RGBQUAD,颜色表长度为 64 字节。
bit_count 为 8 时是 256 色图像,颜色表长度为 1024 字节。

也就是说 RGBQUAD 最多只需要 256 个,每个定义一种颜色,每个颜色 4 字节分别为 B,G,R 和保留字,32 位位图时保留字表示透明度值。

位图数据

记录了位图的每一个像素值的颜色号。从下到上,从左到右。
颜色表的长度并不同于一个点所占的位数,如 2 色图像每个点就只占 1 位。

未压缩的位图数据大小(24 位位图除外)大于或等于实际有效的数据大小。原因如下:

颜色表的长度并不同于一个点所占的位数,如 2 色图像每个点就只占 1 位。

bmp 文件记录一行图像是以字节为单位,所以一个字节中的数据位信息表示的点只会在一行中。譬如 16 色图像每个点占 4 位,而每个字节存储两个点的信息,那么最后一个点就可能独占一个字节。

另外,除了彩色,其他类型时每行字节数要用数据 0 补齐为 4 的整数倍。例如 16 色图宽 19 时,每行就需要补充 2 字节。

另外,当屏幕初始化为 16 或 256 色模式时,必须设置颜色表或者修正颜色值。

观察一个 bmp 文件

直接用 mspaint 打开是图片,改后缀 txt 发现是乱码。这是因为文本文件是 ASCII 码的方式存储的,而 bmp 是位图,二进制文件,所以不能通用编辑器。

通过 VScode 的 Hex Editor 插件,我们可以打开 bmp 这个二进制文件,左边是 16 进制,也就是 4 位,所以两个放在一起是因为两个是一字节 byte。

![[Pasted image 20240924230516.png]]

Windows 的数据是倒着念的,例如 bmp 文件头第一个 2 byte 的 uint16_t type 在这里就是 0x 4D 42,对应到 ASCII 码就是 B M

要读取 bmp 信息的话就是每若干 byte 对应到结构体定义的顺序就好了。一直到位图信息头的字节都是对应好的。

颜色表的个数通过 bit_count 确定,位图数据则是根据 widthheight 以及 bit_count 确定并补全。

为便于观察,首先可以通过画图来获得一个只有几个像素的 bmp 文件。

![[Pasted image 20240925110733.png]]
这里的数据量就很小了,可以手玩得到信息

uint16_t type = 0x4D42 = BM;//位图文件的类型,必须为 BM
uint32_t size = 0x42 = 66;//位图文件的大小,以字节为单位(低位在前)
uint16_t reserved1 = 0x00 = 0;//位图文件保留字,必须为 0
uint16_t reserved2 = 0x00 = 0;//位图文件保留字,必须为 0
uint32_t off_bits = 0x3E = 62;//位图文件的地址偏移,即起始位置,以相对于位图(低位在前)
uint32_t size = 0x28 = 40; //定义该结构体的大小,即为 40
int32_t width = 0x02 = 2; //位图宽度,以像素为单位
int32_t height = 0x01 = 1; //位图高度,以像素为单位
uint16_t planes = 0x01 = 1; //保存所用彩色位面的个数,不常使用
uint16_t bit_count = 0x01 = 1; //保存每个像素的位数,它是图像的颜色深度
uint32_t compression = 0x00 = 0; //定义所用的压缩算法
uint32_t size_image = 0x04 = 4; //位图的大小,以字节为单位。不同于文件大小,这是原始位图数据的大小
uint32_t x_pels_permeter = 0x1274 = 4724;  //位图水平分辨率,每米像素数
uint32_t y_pels_permeter = 0x1274 = 4724; //位图垂直分辨率,每米像素数
uint32_t clr_used = 0x00 = 0; //位图实际使用的颜色表中的颜色数
uint32_t clr_improtant = 0x00 = 0; //位图显示过程中重要的颜色数

接下来两个 RGBQUAD 分别是 B=0、G=0、R=0 的黑色和 B=255、G=255、R=255 的白色,它们的颜色对应值就是 0 和 1。
最后四个字节位置是 62,63,64,65 也就是位图数据。由于这是 2 色图像,所以 1 位即表示一个像素的颜色值,所以第一个字节就能表示出两个像素,又因为每行需要补足 4 的倍数,所以才会有 4 个字节。
这个表示颜色的 0x40 = 64 = 01000000 可以发现两个颜色分别是 0 和 1,也就对应上了。这里我还暂不知道有没有倒着念的规律。

那么如何从这样的二进制文件中读取数据呢?

这里用到若干函数。

fopen():它的原型为 FILE *fopen(const char *__restrict__ _Filename, const char *__restrict__ _Mode,打开一个文件。有两个参数,一个文件名,一个访问模式(r/w),返回一个指针。rb/wb 则是二进制文件的访问模式。

fwrite():它的原型为 size_t fwrite(const void *__restrict__ _Str, size_t _Size, size_t _Count, FILE *__restrict__ _File),向文件写入数据。_Str 是写入数据块的首地址,_Size 是每个元素的长度,_Count 是元素的个数,_File 是使用 fopen() 后得到的指针。

fread():它的原型为 size_t fread(void *__restrict__ _DstBuf, size_t _ElementSize, size_t _Count, FILE *__restrict__ _File),从文件里读取数据。参数含义与 fwrite() 类似。

有了这三个就可以读入 bmp 了。

尝试读取一下上面的两像素位图:

首先把上面的结构体拉下来,然后发现如果不把 bmp_file_header_t 里的 type 单独读入就读的不正确,单独读入后它就会自动填充 uint16_t 和 uint32_t,神奇。查了查这里涉及到结构体内存对齐的问题C语言--结构体内存对齐规则_结构体对齐原则-CSDN博客,不展开写了。然后还发现可以用 rewind() 来让文件的指针重回文件开头。

然后又发现读入颜色表的时候不知为何不能读入 uint8_t,虽然把两个值拼在一起解决了,但是还是怪。同理,位图数据也只能读入了 uint16 再拆,不过这里就算读个 8 位也得拆,就不管了。

如下就将这张 bmp 读入进来可以随意修改了。

#include<bits/stdc++.h>
using namespace std;
#define in read()
inline int read(){
	int p=0,f=1; char c=getchar();
	while(!isdigit(c)){if(c=='-') f=-f; c=getchar();}
	while(isdigit(c)) p=p*10+c-48,c=getchar();
	return p*f;
}
typedef struct{
	//uint16_t type;//位图文件的类型,必须为 BM
	uint32_t size;//位图文件的大小,以字节为单位(低位在前)
	uint16_t reserved1;//位图文件保留字,必须为 0
	uint16_t reserved2;//位图文件保留字,必须为 0
	uint32_t off_bits;//位图文件的地址偏移,即起始位置,以相对于位图(低位在前)
}__attribute__ ((packed)) bmp_file_header_t;

typedef struct{
	uint32_t size; //定义该结构体的大小,即为 40
	int32_t width; //位图宽度,以像素为单位
	int32_t height; //位图高度,以像素为单位
	uint16_t planes; //保存所用彩色位面的个数,不常使用
	uint16_t bit_count; //保存每个像素的位数,它是图像的颜色深度
	uint32_t compression; //定义所用的压缩算法
	uint32_t size_image; //位图的大小,以字节为单位。不同于文件大小,这是原始位图数据的大小
	uint32_t x_pels_permeter;  //位图水平分辨率,每米像素数
	uint32_t y_pels_permeter; //位图垂直分辨率,每米像素数
	uint32_t clr_used; //位图实际使用的颜色表中的颜色数
	uint32_t clr_improtant; //位图显示过程中重要的颜色数
} bmp_info_header_t;

typedef struct _tagRGBQUAR{
	uint16_t rgbBlueAndGreen; //指定蓝色强度
	//uint16_t rgbGreen; //指定绿色强度
	uint16_t rgbRedAndReserved; //指定红色强度
	//uint16_t rgbReserved; //保留,设为 0
} RGBQUAD;


bmp_file_header_t a;
bmp_info_header_t b;
RGBQUAD c[256];

uint16_t mp[5005][5005];
int wd,ht;
signed main(){
	FILE *file = fopen("test.bmp","rb"); 
	uint16_t type;
	fread(&type,2,1,file);
	fread(&a,12,1,file);
	cout<<type<<'\n';
	cout<<a.size<<'\n';
	cout<<a.reserved1<<'\n';
	cout<<a.reserved2<<'\n';
	cout<<a.off_bits<<'\n';
		
	fread(&b,40,1,file);
	cout<<b.size<<'\n';
	cout<<b.width<<'\n';
	cout<<b.height<<'\n';
	cout<<b.planes<<'\n';
	cout<<b.bit_count<<'\n';
	cout<<b.compression<<'\n';
	cout<<b.size_image<<'\n';
	cout<<b.x_pels_permeter<<'\n';
	cout<<b.y_pels_permeter<<'\n';
	cout<<b.clr_used<<'\n';
	cout<<b.clr_improtant<<'\n';
	
	for(int i=0;i<(1<<b.bit_count);i++){
		fread(&c[i],4,1,file);
		cout<<c[i].rgbBlueAndGreen-((c[i].rgbBlueAndGreen>>8)<<8)<<'\n';		
		cout<<(c[i].rgbBlueAndGreen>>8)<<'\n';
		cout<<c[i].rgbRedAndReserved-((c[i].rgbRedAndReserved>>8)<<8)<<'\n';
		cout<<(c[i].rgbRedAndReserved>>8)<<'\n';
	}
	
	fread(&mp,a.size-a.off_bits,1,file);
	
	wd=(b.width*b.bit_count+7)/8;
	wd+=wd%4==0?0:4-wd%4;
	ht=b.height;
	
	for(int i=0;i<ht;i++){
		for(int j=0;j<wd/2;j++){	
			cout<<(mp[i][j]-((mp[i][j]>>8)<<8))<<' '<<(mp[i][j]>>8)<<' ';
		}cout<<'\n';
	}
	
	return 0;
}
posted @ 2024-12-25 17:08  llmmkk  阅读(387)  评论(0)    收藏  举报