文件IO——bmp图像处理
目录
- 前言
- 一、BMP图片原理
- (1)位图文件头
- (2)位图信息头
- (3)调色板
- (4)位图数据
- **对齐说明**
- 存储顺序
- 像素结构
- 二、练习
- 1.生成一个新的图片,将图片等比例缩小为原来的1/2
- 代码实现
- 2.生成一个新图片,只显示左半部分
- 代码实现
前言
学文件IO不仅仅只学文件打开关闭,读写操作,还要懂的一些常用的图片格式的文件存储结构。这对我们后期学习图像处理有很大的帮助。
一、BMP图片原理
(1)位图文件头
位图文件头(Bitmap-File Header)包含了图像类型、图像大小、两个保留字以及位图数据存放地址。
(2)位图信息头
位图信息头(Bitmap-Information Header)包含了位图信息头的大小、图像的宽和高、图像的色深、压缩说明、图像数据的大小和其他一些参数。
(3)调色板
bmp位图按照像素深度分类可以分为:1bit位图(2色)、4bit位图(16色)、8bit位图(256色)、16bit位图(65536色-高彩色)、24bit位图(1670万色-真彩色)、32bit位图(1670万色-增强型真彩色)。
调色板是为了让一些颜色深度比较小(1bit、4bit、8bit)的位图可以表示颜色而设置的。调色板存储颜色,后面的位图数据存储颜色索引,这样调色板+位图数据就可以表示颜色了。
需要注意的是:16bit、24bit、32bit的位图一般没有调色板,因为从16bit开始就直接使用位图数据表示颜色了。
(4)位图数据
位图数据分两种情况,如果带调色板,则位图数据存放的是调色板的颜色索引,如果不带调色板,则位图数据存放实际的argb值。由于24位bmp图片不带调色板,所以文件开头的54字节为图片信息,从第55个字节开始就为bmp图片的颜色数据。
对齐说明
计算机的CPU为了提高工作效率,所以读取数据的时候会进行字节对齐,一般图像刷新都是行为单位,所以CPU每次都是读取一行的大小,所以32bit系统下如果一行字节大小不是4的倍数,就会进行字节补齐,目的是提高效率,是典型的用空间来换时间的案例!
分辨率是10*10,每行10个像素点,每个像素点3字节,所以一行是30个字节,不够4的倍数,所以需要在每行的末尾补2个字节。
由于每行必须是4的倍数,所以需要补齐的字节数有4种情况:0、1、2、3,所以需要设计算法来计算每行需要自动补齐的字节数。 公式: byte = ( 4 - (width*3 % 4) ) % 4
存储顺序
如果打算了解BMP图片内部数据的存储顺序(大端 or 小端),应该去查看BMP文件内部数据结构,需要使用专业工具(HEX十六进制查看器)。
像素结构
二、练习
1.生成一个新的图片,将图片等比例缩小为原来的1/2
代码实现
/** Copyright (c)* * date: 2025-7-26* * author: Charles* * function name : halfBMP.c** function: 把一张任意尺寸的 BMP 图片等比例且不失真的缩小为原来的 1/2并生成一张新的 BMP 图片*/#include <stdio.h>
#include <stdlib.h>
#include <string.h>// BMP文件信息头(14字节)
typedef struct {char bfType[2]; // 文件类型,必须为0x4D42unsigned int bfSize; // 文件大小(字节)unsigned short bfReserved1; // 保留,必须为0unsigned short bfReserved2; // 保留,必须为0unsigned int bfOffBits; // 位图数据偏移量
} __attribute__((packed)) BITMAPFILEHEADER; //__attribute__((packed))防止字节对齐// BMP文件数据头(40字节)
typedef struct {unsigned int biSize; // 结构体大小,40字节int biWidth; // 图像宽度(像素)int biHeight; // 图像高度(像素)unsigned short biPlanes; // 颜色平面数,必须为1unsigned short biBitCount; // 每像素位数unsigned int biCompression; // 压缩类型unsigned int biSizeImage; // 图像数据大小(字节)int biXPelsPerMeter; // 水平分辨率int biYPelsPerMeter; // 垂直分辨率unsigned int biClrUsed; // 实际使用的颜色数unsigned int biClrImportant; // 重要颜色数
} __attribute__((packed)) BITMAPINFOHEADER; //__attribute__((packed))防止字节对齐int main(int argc, char const *argv[])
{BITMAPFILEHEADER fileheader_src, fileheader_dest; //源图片的文件头和信息头BITMAPINFOHEADER infoheader_src, infoheader_dest; //目标图片的文件头和信息头if(argc != 3){ //如果参数不为3就输入错误printf("argument is invailed!\n");exit(-1);}FILE* file_src = fopen(argv[1], "rb"); //以二进制只读的方式打开源图片if(!file_src){ //打开源图片的错误处理printf("file: %s open failed\n",argv[1]);exit(-1);}fread(&fileheader_src, sizeof(fileheader_src), 1, file_src); //将源图片的文件头和信息头读入源图片文件头和信息头的结构体变量中fread(&infoheader_src, sizeof(infoheader_src), 1, file_src);if(fileheader_src.bfType[0] != 'B' || fileheader_src.bfType[1] != 'M'){ //判断源文件是否为BMP图片printf("file: %s is not BMP\n", argv[1]);fclose(file_src);exit(-1);}printf("file_src %s size is %d byte\n", argv[1], fileheader_src.bfSize); //输出源图片大小,以及像素宽度和高度printf("file_src %s width is %d px\n", argv[1], infoheader_src.biWidth);printf("file_src %s height is %d px\n", argv[1], infoheader_src.biHeight);fileheader_dest = fileheader_src; //将源文件文件头和信息头信息赋值给目标图片infoheader_dest = infoheader_src;int new_width = infoheader_src.biWidth / 2; //目标图片新的像素宽度和高度int new_height = infoheader_src.biHeight / 2;int src_stride = ((infoheader_src.biWidth * 3 + 3) & ~3); //源图片与目标图片的行对齐字节数int dest_stride = ((new_width * 3 + 3) & ~3);infoheader_dest.biWidth = new_width; //目标图片新的数据信息infoheader_dest.biHeight = new_height;infoheader_dest.biSizeImage = dest_stride * new_height;fileheader_dest.bfSize = dest_stride * new_height + 54;fileheader_dest.bfOffBits = 54;FILE* file_dest = fopen(argv[2], "wb"); //以二进制写的方式打开目标图片,如果不存在该图片就创建if(!file_dest){printf("file: %s open failed\n",argv[2]);fclose(file_src);exit(-1);}fwrite(&fileheader_dest, sizeof(fileheader_dest), 1, file_dest); //将目标图片结构体信息写入目标图片中fwrite(&infoheader_dest, sizeof(infoheader_dest), 1, file_dest);unsigned char* src_line = (unsigned char*)malloc(src_stride); //存储原图片与目标图片每行的数据缓冲区unsigned char* dest_line = (unsigned char*)malloc(dest_stride);if(!src_line || !dest_line){ //分配内存错误处理printf("malloc is failed!\n");fclose(file_src);fclose(file_dest);exit(-1);}fseek(file_src, fileheader_src.bfOffBits, SEEK_SET);for(int y = 0; y < new_height; y++){fread(src_line, 1, src_stride, file_src); //读源图片每行的像素点BGR,BMP图片是以小端的形式存储if(y < infoheader_src.biHeight)fseek(file_src, src_stride, SEEK_CUR); //每读一行隔一行for(int x = 0; x < new_width; x++){ int src_index = x * 3 * 2; //因为目标图片缩小为源图片1/2, 所以这里2倍偏移int dest_index = x * 3;dest_line[dest_index + 0] = src_line[src_index + 0]; //Bdest_line[dest_index + 1] = src_line[src_index + 1]; //Gdest_line[dest_index + 2] = src_line[src_index + 2]; //R}fwrite(dest_line, 1, dest_stride, file_dest); //将像素点写入目标图片中memset(dest_line, 0, dest_stride); //清空数据}printf("file_dest %s size is %d byte\n", argv[2], fileheader_dest.bfSize);printf("file_dest %s width is %d px\n", argv[2], infoheader_dest.biWidth);printf("file_dest %s height is %d px\n", argv[2], infoheader_dest.biHeight);free(src_line);free(dest_line);fclose(file_src);fclose(file_dest);return 0;
}
2.生成一个新图片,只显示左半部分
代码实现
/** Copyright (c)* * date: 2025-7-26* * author: Charles* * function name : halfBMP.c** function: 把一张任意尺寸的 BMP 图片生成一张只显示原来图片的左半部分*/#include <stdio.h>
#include <stdlib.h>
#include <string.h>// BMP文件信息头(14字节)
typedef struct {char bfType[2]; // 文件类型,必须为0x4D42unsigned int bfSize; // 文件大小(字节)unsigned short bfReserved1; // 保留,必须为0unsigned short bfReserved2; // 保留,必须为0unsigned int bfOffBits; // 位图数据偏移量
} __attribute__((packed)) BITMAPFILEHEADER; //__attribute__((packed))防止字节对齐// BMP文件数据头(40字节)
typedef struct {unsigned int biSize; // 结构体大小,40字节int biWidth; // 图像宽度(像素)int biHeight; // 图像高度(像素)unsigned short biPlanes; // 颜色平面数,必须为1unsigned short biBitCount; // 每像素位数unsigned int biCompression; // 压缩类型unsigned int biSizeImage; // 图像数据大小(字节)int biXPelsPerMeter; // 水平分辨率int biYPelsPerMeter; // 垂直分辨率unsigned int biClrUsed; // 实际使用的颜色数unsigned int biClrImportant; // 重要颜色数
} __attribute__((packed)) BITMAPINFOHEADER; //__attribute__((packed))防止字节对齐int main(int argc, char const *argv[])
{BITMAPFILEHEADER fileheader_src, fileheader_dest; //源图片的文件头和信息头BITMAPINFOHEADER infoheader_src, infoheader_dest; //目标图片的文件头和信息头if(argc != 3){ //如果参数不为3就输入错误printf("argument is invailed!\n");exit(-1);}FILE* file_src = fopen(argv[1], "rb"); //以二进制只读的方式打开源图片if(!file_src){ //打开源图片的错误处理printf("file: %s open failed\n",argv[1]);exit(-1);}fread(&fileheader_src, sizeof(fileheader_src), 1, file_src); //将源图片的文件头和信息头读入源图片文件头和信息头的结构体变量中fread(&infoheader_src, sizeof(infoheader_src), 1, file_src);if(fileheader_src.bfType[0] != 'B' || fileheader_src.bfType[1] != 'M'){ //判断源文件是否为BMP图片printf("file: %s is not BMP\n", argv[1]);fclose(file_src);exit(-1);}printf("file_src %s size is %d byte\n", argv[1], fileheader_src.bfSize); //输出源图片大小,以及像素宽度和高度printf("file_src %s width is %d px\n", argv[1], infoheader_src.biWidth);printf("file_src %s height is %d px\n", argv[1], infoheader_src.biHeight);fileheader_dest = fileheader_src; //将源文件文件头和信息头信息赋值给目标图片infoheader_dest = infoheader_src;int new_width = infoheader_src.biWidth / 2; //目标图片新的像素宽度int height = infoheader_src.biHeight;int src_stride = ((infoheader_src.biWidth * 3 + 3) & ~3); //源图片与目标图片的行对齐字节数int dest_stride = ((new_width * 3 + 3) & ~3);infoheader_dest.biWidth = new_width; //目标图片新的数据信息infoheader_dest.biSizeImage = dest_stride * height;fileheader_dest.bfSize = dest_stride * height + 54;fileheader_dest.bfOffBits = 54;FILE* file_dest = fopen(argv[2], "wb"); //以二进制写的方式打开目标图片,如果不存在该图片就创建if(!file_dest){printf("file: %s open failed\n",argv[2]);fclose(file_src);exit(-1);}fwrite(&fileheader_dest, sizeof(fileheader_dest), 1, file_dest); //将目标图片结构体信息写入目标图片中fwrite(&infoheader_dest, sizeof(infoheader_dest), 1, file_dest);unsigned char* src_line = (unsigned char*)malloc(src_stride); //存储原图片与目标图片每行的数据缓冲区unsigned char* dest_line = (unsigned char*)malloc(dest_stride);if(!src_line || !dest_line){ //分配内存错误处理printf("malloc is failed!\n");fclose(file_src);fclose(file_dest);exit(-1);}fseek(file_src, fileheader_src.bfOffBits, SEEK_SET);for(int y = 0; y < height; y++){fread(src_line, 1, src_stride, file_src); //读源图片每行的像素点BGR,BMP图片是以小端的形式存储for(int x = 0; x < new_width; x++){ int src_index = x * 3 ; //因为目标图片缩小为源图片1/2, 所以这里2倍偏移int dest_index = x * 3;dest_line[dest_index + 0] = src_line[src_index + 0]; //Bdest_line[dest_index + 1] = src_line[src_index + 1]; //Gdest_line[dest_index + 2] = src_line[src_index + 2]; //R}fwrite(dest_line, 1, dest_stride, file_dest); //将像素点写入目标图片中memset(dest_line, 0, dest_stride); //清空数据}printf("file_dest %s size is %d byte\n", argv[2], fileheader_dest.bfSize);printf("file_dest %s width is %d px\n", argv[2], infoheader_dest.biWidth);printf("file_dest %s height is %d px\n", argv[2], infoheader_dest.biHeight);free(src_line);free(dest_line);fclose(file_src);fclose(file_dest);return 0;
}
希望各位靓仔靓女点赞,收藏,关注多多支持,我们共同进步,后续我会更新更多的面试真题,你们的支持将是我前进最大的动力