2 - BMP 图片的读取与保存

前言:在本文博客中,参考资料资料如下所引:
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() 来判定到底出现了什么情况。

③、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() 来判定到底出现了什么情况。

④、fseek

  • 函数原型
int fseek(FILE *fp, long offset, int base)
  • 功能说明
     使文件指针fp移到基于base的相对位置offset处。
  • 参数说明
    (1)fp:文件指针。
    (2)offset:相对base的字节位移量。这是个长整数,用以支持大于64KB的文件。
    (3)base:文件位置指针移动的基准位置,是计算文件位置指针位移的基点。其中参数说明如下表所示。
文件开头SEEK_SET0
文件当前指针位置SEEK_CUR1
文件末尾SEEK_END2
  • 返回值
    正常返回:当前指针位置。
    异常返回:-1,表示定位操作出错。

3、图片的读取

  1. 读取文件,并判断文件是否读取成功
  2. 读取位图文件头
    • 位图文件头结构体变量、并指明字节、读取相关信息
    • 判断文件的类型、是否是 BMP 格式的图片文件
    • 获取实际的字节偏移,并不是所有的 24 位真彩图的字节偏移都是 54,当字节偏移不正确的时候,会导致真实的位图信息数据错误,在以标准的 54 字节存入的时候,有可能会发生整体偏移等问题,如下图所示。
      输入错误的字节偏移之后,读取的位图文件
  3. 读取位图信息头
    • 获取图像的宽度、高度等信息
    • 获取像素位数
  4. 读取调色板信息
  5. 跳过字节偏移,防止图片格式问题造成的真实偏移错误
  6. 读取真实的位图信息

4、图片的保存

  1. 创建输出文件
  2. 判断像素位数,是否需要增加颜色表数据字节
  3. 计算真实行字节
  4. 定义有效的位图文件头数据
  5. 定义有效的位图信息头数据
  6. 再次判断像素位数,需要填写颜色表数据
  7. 写入真实图片数据

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;
}

测试图片如下图所示:
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值