以 bmp 格式图片为例的 c 读入和操作文件
C语言读取和存储bmp格式图片_c 保存bmp-CSDN博客
用Visual Studio查看图片的二进制流-CSDN博客
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 确定,位图数据则是根据 width,height 以及 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;
}

浙公网安备 33010602011771号