欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

读取bmp格式位图文件到二维数组(C语言)

程序员文章站 2022-03-24 17:44:38
...

本来是打算弄个BadApple玩玩,不过不满足于简单地读取文本文件并输出,所以最后变成了研究如何用C语言读取位图文件并通过二维数组来存储像素信息。

第一步自然是弄清楚bmp的文件格式。在各种位图格式中,bmp因为数据块通常没有压缩,每个像素都由独立的几个或者几组bits来表示,读写方式都比较简单,只需要按照格式要求通过fread读取二进制文件就行。

bmp文件大概有四部分组成,第一部分是文件类型数据,存放跟bmp文件类型有关的信息,共占用14字节。第二部分是图像信息数据,保存位图图像相关的信息,共40字节,第三部分是一个可选的调色板,最后是像素信息区。

各部分中具体的字段含义见下表:

读取bmp格式位图文件到二维数组(C语言)

按照上面的格式说明,我们定义几个结构体来描述他们:

#pragma once

#include<stdio.h>
typedef unsigned int DWORD;  // 4bytes
typedef unsigned short WORD;  // 2bytes
typedef signed long LONG;  // 4bytes
typedef unsigned char BYTE;  // 1bytes


#pragma pack(push)
#pragma pack(1)// 修改默认对齐值
/*位图文件文件头结构体*/
typedef struct tagBITMAPFILEHEADER {
	WORD bFileType;
	DWORD bFileSize;
	WORD bReserved1;
	WORD bReserved2;
	DWORD bPixelDataOffset;
}BITMAPFILEHEADER; //14bytes
#pragma pack(pop)

/*位图文件信息头结构体*/
typedef struct tagBITMAPINFOHEADER {
	DWORD bHeaderSize;  // 图像信息头总大小(40bytes)
	LONG bImageWidth;  // 图像宽度(像素)
	LONG bImageHeight;  // 图像高度
	WORD bPlanes;  // 应该是0
	WORD bBitsPerPixel;  // 像素位数
	DWORD bCompression;  // 图像压缩方法
	DWORD bImageSize;  // 图像大小(字节)
	LONG bXpixelsPerMeter;  // 横向每米像素数
	LONG bYpixelsPerMeter;  // 纵向每米像素数
	DWORD bTotalColors;  // 使用的颜色总数,如果像素位数大于8,则该字段没有意义
	DWORD bImportantColors;  // 重要颜色数,一般没什么用
}BITMAPINFOHEADER; //40bytes

/*位图文件调色板结构体*/
typedef struct tagRGBQUAD {
	BYTE	rgbBlue;
	BYTE	rgbGreen;
	BYTE	rgbRed;
	BYTE	rgbReserved;
}RGBQUAD;

/*像素点RGB结构体*/
typedef struct tagRGB {
	BYTE blue;
	BYTE green;
	BYTE red;
}RGBDATA;

#pragma pack(push)
#pragma pack(1)    
用于取消结构体的自动对齐,如果不指定文件头结构体可能占据的大小不是14字节,关于机构体的大小是否正确可以通过输出 sizeof(BITMAPFILEHEADER) 来得到。

事实上这些机构体在wingdi.h文件中也有定义,如果使用windows也可以直接使用gdi中定义的结构。

定义好这些结构体之后就是编写读取位图的函数了,基本方法就是通过fread来从文件读取指定大小,指定个数的元素,比如下面这条语句用于读取信息头:

fread(infoHead, sizeof(BITMAPINFOHEADER), 1, fp)

从fp文件流中读取14字节的元素一次,并存放到infoHead指针所指向的内存区域,infoHead是BITMAPINFOHEADER类型的结构指针,该内存可以通过malloc在堆中动态分配也可以直接定义相应变量。

下面贴上用到的源码,原谅我没有写多少注释:

#define _CRT_SECURE_NO_DEPRECATE
#include"bmp.h"
#include<string.h>
#include<stdlib.h>
#include<stdio.h>


FILE* openBmpImage(char* fileName, char* mode) {
	FILE* fp;
	if (strcmp(mode, "r") == 0) {
		mode = "rb";
	}
	else if (strcmp(mode,"w") == 0) {
		mode = "ab";
	}
	else {
		//输出错误信息
		fprintf(stderr,"文件打开模式:%s使用错误\n",mode);
		//文件打开失败,返回空指针
		return NULL;
	}
	if ((fp = fopen(fileName,mode)) == NULL) {
		fprintf(stderr, "打开文件:%s失败\n", fileName);
		return NULL;	
	}
	return fp;
}

void closeBmpImage(FILE* fp) {
	//关闭文件
	fclose(fp);
	//printf("已关闭文件\n");
	//释放文件指针
	free(fp);
	//printf("已释放文件指针\n");
}

BITMAPFILEHEADER* readBmpFileHead(FILE* fp) {
	//printf("%d\n", sizeof(BITMAPFILEHEADER));//这个大小是16Bytes没错
	BITMAPFILEHEADER* fileHead = (BITMAPFILEHEADER*)malloc(sizeof(BITMAPFILEHEADER));
	if (fileHead == NULL) {
		fprintf(stderr,"内存分配失败");
	}
	if (fread(fileHead, sizeof(BITMAPFILEHEADER), 1, fp) != 1) {
		fprintf(stderr,"文件头读取失败");
	}
	return fileHead;
}

BITMAPINFOHEADER* readBmpInfoHead(FILE* fp) {
	//printf("%d\n", sizeof(BITMAPINFOHEADER));
	BITMAPINFOHEADER* infoHead = (BITMAPINFOHEADER*)malloc(sizeof(BITMAPINFOHEADER));
	if (infoHead == NULL) {
		fprintf(stderr, "内存分配失败");
	}
	if (fread(infoHead, sizeof(BITMAPINFOHEADER), 1, fp) != 1) {
		fprintf(stderr, "信息头读取失败");
	}
	printf("信息头大小:%d字节\n", infoHead->bHeaderSize);
	printf("图片宽度:%d像素\n", infoHead->bImageWidth);
	printf("图片高度:%d像素\n", infoHead->bImageHeight);
	printf("颜色位数:%d位\n", infoHead->bBitsPerPixel);
	printf("横向每米像素数:%d个\n", infoHead->bXpixelsPerMeter);
	printf("纵向每米像素数:%d个\n", infoHead->bYpixelsPerMeter);
	printf("数据块大小:%d字节\n", infoHead->bImageSize);
	printf("位面数:%d\n", infoHead->bPlanes);
	printf("使用颜色总数:%d个\n", infoHead->bTotalColors);
	printf("重要颜色总数:%d个\n", infoHead->bImportantColors);
	printf("压缩算法:%d\n", infoHead->bCompression);
	
	return infoHead;
}

RGBDATA** readBmpDataToArr(FILE* fp) {
	int i = 0, j = 0;
	int width = 0, height = 0;
	BITMAPFILEHEADER* fileHead = readBmpFileHead(fp);
	BITMAPINFOHEADER* infoHead = readBmpInfoHead(fp);
	width = infoHead->bImageWidth;
	height = infoHead->bImageHeight;
	RGBDATA** data = createMatrix(width,height);
	//如果位数小于8则调色板有效
	if (infoHead->bBitsPerPixel < 8) {
		RGBQUAD* rgbQuad = (RGBQUAD*)malloc(sizeof(RGBQUAD));
		if(rgbQuad == NULL){
			printf("内存分配失败");
		}
		if (fread(rgbQuad, sizeof(rgbQuad), 1, fp) != 1) {
			printf("调色板读入失败");
		}
	}
	for (i = 0; i < height; i++) {
		for (j = 0; j < width; j++) {
			fread(&data[i][j], sizeof(RGBDATA), 1, fp);
		}
	}
	return data;
}

RGBDATA** createMatrix(int width,int height) {
	//动态创建二维数组
	RGBDATA** Matrix;
	int i;
	
	Matrix = (RGBDATA **)malloc(sizeof(RGBDATA*) * height);
	if (Matrix == NULL) {
		fprintf(stderr,"内存分配失败");
		return NULL;
	}
	
	for (i = 0; i < height; i++) {
		Matrix[i] = (RGBDATA *)malloc(sizeof(RGBDATA) * width);
		if (Matrix[i] == NULL) {
			fprintf(stderr, "内存分配失败");
			return NULL;
		}
	}
	return(Matrix);
}

没有太多内容值得细说,后面是测试代码:

#include<stdio.h>
#include"bmp.h"
int main() {
	//printf("%d",sizeof(unsigned short));
	//printf("%d",sizeof(unsigned int));
	//printf("%d",sizeof(unsigned long));
	//printf("%d",sizeof(unsigned char));
	FILE* fp = openBmpImage("lena.bmp","r");
	//BITMAPFILEHEADER* fileHead = readBmpFileHead(fp);
	//BITMAPINFOHEADER* infoHead = readBmpInfoHead(fp);
	RGBDATA ** data = readBmpDataToArr(fp);
	//谨慎,避免下标越界
	for (int i = 0; i < 512; i++) {
		for (int j = 0; j < 512; j++) {
			printf("第(%d,%d)像素:[%d,%d,%d] \n ", 511-i,j+1,data[i][j].blue, data[i][j].green, data[i][j].red);
		}
		printf("\n");
	}
	closeBmpImage(fp);
	getchar();
	return 0;
}

运行之后可以得到这样的结果:

读取bmp格式位图文件到二维数组(C语言)注意每个像素得到的三个数值分别代表b,g,r分量,和习惯的rgb是反着的。

最后要注意,大多数位图的像素信息是自下而上的,也就是说代码中得到的数组的最后一行对应图片的第一行,以此类推。。

位图读取就是这样了,下一步再考虑写入bmp文件和像素数组的处理操作。。。