前言:在本文博客中,参考资料资料如下所引:
1、C语言文件操作
2、pragma pack(1) 的解释来源
如有错误,恳请指出。
1、让编译器将结构体数据强制连续排列
#pragma pack(1)
- 作用:让编译器将结构体数据强制连续排列
- 解释:例如定义下面这样的结构体
struct s {
char ch;
int i;
};
然后在主函数中写一句:printf("%d", sizeof(struct s))
;也就是输出结构体s
所占的字节数。
你觉得输出结果会是多少呢?我们知道,char
型占用 1 个字节,int
型占 4 个字节,那么输出的结果是 5 吗?
答案是错误的,输出结果为8。
因为编译器为了让程序跑得更快,减少CPU读取数据的指令周期,对结构体的存储进行了优化。实际上第一个char
型成员虽然本来只有 1 个字节,但实际上却占用掉了 4 个字节,为的是让第二个int
型成员的地址能够被 4 整除。因此实际占用的是 8 个字节。
- 结论:#pragma pack(1) 就是为了让编译器将结构体数据强制连续排列读取,这样的话,sizeof(struct s)输出的结果就是5了。
- 常用说明:
#pragma pack(push) // 保存对齐状态
#pragma pack(n) // 设定为 n 字节对齐
#pragma pack(pop) // 恢复对齐状态
2、文件操作函数的介绍
①、fopen
- 函数原型
FILE *fopen(char *pname, char *mode)
- 功能说明
(1)按照mode
规定的方式,打开由pname
指定的文件。若找不到由pname
指定的相应文件,就按以下方式之一处理:
(2) 此时如mode
规定按写方式打开文件,就按由pname
指定的名字建立一个新文件;
(3) 此时如mode
规定按读方式打开文件,就会产生一个错误。 - 打开文件的作用是:
(1)分配给打开文件一个FILE 类型的文件结构体变量,并将有关信息填入文件结构体变量;
(2)开辟一个缓冲区;
(3)调用操作系统提供的打开文件或建立新文件功能,打开或建立指定文件; - 参数说明:
(1)pname:是一个字符指针,它将指向要打开或建立的文件的文件名字符串。
(2)mode:是一个指向文件处理方式字符串的字符指针。 - 返回值
(1)正常返回:被打开文件的文件指针。(FILE *)
(2)异常返回:NULL,表示打开操作不成功。
②、fread
- 函数原型
int fread(void *buffer, unsigned size_of, unsigned count, FILE *fp)
- 功能说明
以二进制形式读取文件中的数据。从fp
指定的文件流数据中,按二进制形式将size_of * count
个数据从buffer
指向的数据区中读取出来。 - 参数说明
(1)buffer
:这是一个void型指针,指出要将读入数据存放在其中的存储区首地址。
(2)size_of
:指出一个数据块的字节数,即一个数据块的大小尺寸。
(3)count
:是读取次数 。
(4)fp
:这是个文件指针,指出要从其中读出数据的文件。 - 返回值,随着调用格式的不同而不同:
- 调用方法 1、
fread(buffer, sizeof(buffer), 1, fp)
;
读取成功时,即当读取的数据量正好是sizeof(buffer)
个Byte
时,返回值为 1。(即count
);否则返回值为 0。(说明读取数据量小于sizeof(buffer)
) - 调用方法 2、
fread(buffer, 1, sizeof(buffer), fp)
;
读取成功时,返回值为实际读回的数据个数sizeof(buffer)
。(单位为Byte) 如果文件中剩下的数据块个数少于参数中count
(即sizeof(buffer)
)指出的个数,或者发生了错误,返回 0 值。 - 异常返回:此时可以用
feof()
和在ferror()
来判定到底出现了什么情况。
- 调用方法 1、
③、fwrite
- 函数原型
int fwrite(void *buffer, unsigned size_of, unsigned count, FILE *fp)
- 功能说明
以二进制形式写数据到文件中去,按二进制形式,将buffer
指定的数据缓冲区内的size_of * count
个数据写入fp
指定的文件数据中。 - 参数说明
(1)buffer
:这是一个void型指针,指出要将读入数据存放在其中的存储区首地址。
(2)size_of
:指出一个数据块的字节数,即一个数据块的大小尺寸。
(3)count
:是写入次数 。
(4)fp
:这是个文件指针,指出要从其中读出数据的文件。 - 返回值,随着调用格式的不同而不同:
- 调用方法 1、
fwrite(buffer, sizeof(buffer), 1, fp)
;
写入成功时,即当写入的数据量正好是sizeof(buffer)
个Byte
时,返回值为 1。(即count
);否则返回值为 0。 - 调用方法 2、
fwrite(buffer, 1, sizeof(buffer), fp)
;
写入成功时,返回值为实际写入的数据个数sizeof(buffer)
。(单位为Byte) 如果发生了错误,返回 0 值。 - 异常返回:此时可以用
feof()
和在ferror()
来判定到底出现了什么情况。
- 调用方法 1、
④、fseek
- 函数原型
int fseek(FILE *fp, long offset, int base)
- 功能说明
使文件指针fp
移到基于base
的相对位置offset
处。 - 参数说明
(1)fp
:文件指针。
(2)offset
:相对base的字节位移量。这是个长整数,用以支持大于64KB的文件。
(3)base
:文件位置指针移动的基准位置,是计算文件位置指针位移的基点。其中参数说明如下表所示。
文件开头 | SEEK_SET | 0 |
---|---|---|
文件当前指针位置 | SEEK_CUR | 1 |
文件末尾 | SEEK_END | 2 |
- 返回值
正常返回:当前指针位置。
异常返回:-1,表示定位操作出错。
3、图片的读取
- 读取文件,并判断文件是否读取成功
- 读取位图文件头
- 位图文件头结构体变量、并指明字节、读取相关信息
- 判断文件的类型、是否是 BMP 格式的图片文件
- 获取实际的字节偏移,并不是所有的 24 位真彩图的字节偏移都是 54,当字节偏移不正确的时候,会导致真实的位图信息数据错误,在以标准的 54 字节存入的时候,有可能会发生整体偏移等问题,如下图所示。
- 读取位图信息头
- 获取图像的宽度、高度等信息
- 获取像素位数
- 读取调色板信息
- 跳过字节偏移,防止图片格式问题造成的真实偏移错误
- 读取真实的位图信息
4、图片的保存
- 创建输出文件
- 判断像素位数,是否需要增加颜色表数据字节
- 计算真实行字节
- 定义有效的位图文件头数据
- 定义有效的位图信息头数据
- 再次判断像素位数,需要填写颜色表数据
- 写入真实图片数据
5、程序示例
#pragma pack(1) // 让编译器将结构体数据强制连续排列
#include <iostream>
using namespace std;
// 计算真实行字节
#define WidthBytes(bits) (((bits) + 31) / 32 * 4)
// 相关宏定义
typedef unsigned char uint_8; // 一个字节
typedef unsigned short uint_16; // 两个字节
typedef unsigned int uint_32; // 四个字节
// 位图文件头,固定大小为 14 个字节,用于判断 Bmp 图片类型,以及文件大小和位图起始位置等
typedef struct BITMAPFILEHEADER {
uint_16 bfType; // 文件类型,Bmp 文件类型前的头两个字节指明必须是0x424D,即字符串“BM”;否则认为不是标准的 Bmp 文件类型
uint_32 bfSize; // 说明文件的大小,包括这14个字节,以字节为单位
uint_16 bfReserved1; // 保留,设置为0
uint_16 bfReserved2; // 保留,设置为0
uint_32 bfOffsetBytes; // 说明从 位图文件头(BITMAPFILEHEADER)结构开始到实际的图像数据之间的字节偏移量,以字节为单位
} bitBmpFileHeader;
//位图信息头,固定大小为40个字节,用于说明位图的尺寸等信息
typedef struct BITMAPINFOHEADER {
uint_32 biSize; // 信息头大小
uint_32 biWidth; // 图像宽度
uint_32 biHeight; // 图像高度
uint_16 biPlanes; // 位平面数,必须为1
uint_16 biBitCount; // 每像素位数。常用的值为1(黑白二色图), 4(16色图), 8(256色), 24(真彩色图)或32(新的.bmp格式支持32位色)
uint_32 biCompression; // 压缩类型:有效的值为BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS(都是一些Windows定义好的常量)
uint_32 biSizeImage; // 压缩图像大小字节数 biSizeImage=biWidth’ × biHeight,biWidth’必须是4的整倍数,(所以不是biWidth,而是biWidth’,表示大于或等于biWidth的,最接近4的整倍数)
uint_32 biXPelsPerMeter; // 水平分辨率,单位是每米的象素个数
uint_32 biYPelsPerMeter; // 垂直分辨率,单位是每米的象素个数
uint_32 biClrUsed; // 位图实际用到的色彩数,如果该值为零,则用到的颜色数为2^biBitCount
uint_32 biClrImportant; // 本位图中重要的色彩数,如果该值为零,则认为所有的颜色都是重要的。
} bitBmpInfoHeader;
// 调色板信息
typedef struct BITMAPRGBAlpha {
uint_8 rgbBlue; // 该颜色的蓝色分量
uint_8 rgbGreen; // 该颜色的绿色分量
uint_8 rgbRed; // 该颜色的红色分量
uint_8 rgbAlpha; // 该颜色的透明分量
} bmpColorPalette;
/************************************************************
*Function: readBmpFile
*Description: 读取 24位真彩图 BMP 图片 或者 8 位灰度图片
*Params: fileName - 图片路径,eg:"E:/壁纸/bmpText.bmp"
* width - 图片宽度信息
* height - 图片高度信息
*Return: imgbuffer - 24 位真彩图数据,忽略调色板信息
************************************************************/
uint8_t* readBmpFile(char* fileName, uint_32* width, uint_32* height)
{
// 读取文件
FILE* readBmpFile = fopen(fileName, "rb");
if (nullptr == readBmpFile) {
printf("文件打开失败,请检查该文件\n");
exit(-1); // 当文件打开失败的时候,直接异常退出该程序
}
printf("文件打开成功\n");
// 定义位图文件头
bitBmpFileHeader bmpFileHeader;
// 读取 位图文件头,固定 14 个字节
fread(&bmpFileHeader, 14, 1, readBmpFile);
// 判断读取出来的文件类型是否是 BMP 文件
if (bmpFileHeader.bfType != 0x4d42) { // 在读取中,低位在前,故该十六进制中则是0x4d42
printf("该文件不是标准 .bmp 格式文件,请重新指定该格式文件!");
exit(-1);
}
printf("Bmp 格式文件读取成功\n");
printf("该图片的字节偏移为 bmpFileHeader.bfOffsetBytes = %d\n", bmpFileHeader.bfOffsetBytes); // 打印真实字节偏移
// 定义位图信息头
bitBmpInfoHeader bmpInfoHeader;
// 读取 位图信息头,固定 40 个字节
fread(&bmpInfoHeader, 40, 1, readBmpFile);
// 获取位图的宽度、高度信息
*width = bmpInfoHeader.biWidth;
*height = bmpInfoHeader.biHeight;
// 读取像素位数,判断是否是 24 位真彩图
if (bmpInfoHeader.biBitCount != 24) {
printf("该 .bmp 格式文件不是 24 位真彩图,请重新指定该格式文件!");
exit(-1);
}
// 从图片开始计算,跳过字节偏移,防止图片格式问题造成的真实偏移错误
fseek(readBmpFile, bmpFileHeader.bfOffsetBytes, 0);
// 读取图像数据
uint8_t* imgBuffer = new uint8_t[bmpInfoHeader.biSizeImage]; // 开辟空间
fread(imgBuffer, sizeof(uint8_t), bmpInfoHeader.biSizeImage, readBmpFile); // 读取位图图像数据
fclose(readBmpFile); // 关闭文件
printf("============================================================================================================\n");
return imgBuffer;
}
/************************************************************
*Function: writeBmpFile
*Description: 保存 BMP 图片
*Params: fileName - 图片路径,eg:"E:/壁纸/bmpTextResult.bmp"
* imgData - 位图数据
* width - 图像宽度
* height - 图像高度
* bitCount - 图像的每像素位数
*Return: None
************************************************************/
void writeBmpFile(char* fileName, uint8_t* imgData, uint32_t width, uint32_t height, uint16_t bitCount)
{
// 定义文件
FILE* writeBmpFile = fopen(fileName, "wb");
if (writeBmpFile == nullptr) {
printf("文件写入失败,请重试\n");
exit(-1);
}
// 颜色表,24 位真彩图不具有颜色表,8 位灰度图像需要填写颜色表
int colorTablesize = 0;
if (bitCount == 8) {
colorTablesize = 1024;
}
//待存储图像数据每行字节数为4的倍数
int lineByte = WidthBytes(width * bitCount);
//申请位图文件头结构变量,填写文件头信息
BITMAPFILEHEADER bitMawriteBmpFileileHeader;
bitMawriteBmpFileileHeader.bfType = 0x4D42;
bitMawriteBmpFileileHeader.bfSize = 54 + colorTablesize + lineByte * height;
bitMawriteBmpFileileHeader.bfReserved1 = 0;
bitMawriteBmpFileileHeader.bfReserved2 = 0;
bitMawriteBmpFileileHeader.bfOffsetBytes = 54 + colorTablesize;
//写文件头进文件
fwrite(&bitMawriteBmpFileileHeader, sizeof(BITMAPFILEHEADER), 1, writeBmpFile);
//申请位图信息头结构变量,填写信息头信息
BITMAPINFOHEADER bitMapInfoHeader;
bitMapInfoHeader.biBitCount = bitCount;
bitMapInfoHeader.biClrImportant = 0;
bitMapInfoHeader.biClrUsed = 0;
bitMapInfoHeader.biCompression = 0;
bitMapInfoHeader.biHeight = height;
bitMapInfoHeader.biPlanes = 1;
bitMapInfoHeader.biSize = 40;
bitMapInfoHeader.biSizeImage = lineByte * height;
bitMapInfoHeader.biWidth = width;
bitMapInfoHeader.biXPelsPerMeter = 0;
bitMapInfoHeader.biYPelsPerMeter = 0;
//写位图信息头进内存
fwrite(&bitMapInfoHeader, sizeof(BITMAPINFOHEADER), 1, writeBmpFile);
//如果 8位灰度图像,有颜色表,写入文件
if (bitCount == 8)
{
BITMAPRGBAlpha* bitMapRGBAlpha = new BITMAPRGBAlpha[256];
for (int i = 0; i < 256; i++) {
bitMapRGBAlpha[i].rgbBlue = i;
bitMapRGBAlpha[i].rgbGreen = i;
bitMapRGBAlpha[i].rgbRed = i;
bitMapRGBAlpha[i].rgbAlpha = 0;
}
fwrite(bitMapRGBAlpha, sizeof(BITMAPRGBAlpha), 256, writeBmpFile);
}
// 写入位图数据
fwrite(imgData, sizeof(uint8_t), bitMapInfoHeader.biSizeImage, writeBmpFile);
// 关闭文件
fclose(writeBmpFile);
}
int main()
{
uint_32 width = 0, height = 0;
// 定义文件路径
char inputImgPath[] = "/home/user/Pictures/图像处理/bmpText.bmp";
char outputImgPath[] = "/home/user/Pictures/图像处理/bmpTextResult.bmp";
uint8_t* imgData = readBmpFile(inputImgPath, &width, &height);
writeBmpFile(outputImgPath, imgData,width, height, 24);
printf("图像处理结束\n");
return 0;
}
测试图片如下图所示: