读取Bitmap(设备无关位图)数据的经典C代码

最近为了测试C6678需要读取图像,考虑到效率问题,我用C语言写了一个读取Bitmap的子程序.

这个程序我之前写过,然后这里简单修改了一下以方便C6678在CCS上测试。

本文先简要介绍这段程序,具体测试请关注后续博文。

这段程序是通用的,可以在别处引用,具体代码如下。

头文件Bitmapper.h

/*
* Bitmapper.h
*
*  Created on: 2014年12月18日
*      Author: fengyhack
*/

#ifndef BITMAPPER_H
#define BITMAPPER_H

#include <stdio.h>
#include <stdlib.h>
//#include <memory.h>

#pragma warning(disable:4996)

typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef long LONG;
typedef int BOOL;

#define TRUE 1
#define FALSE 0

#ifndef NULL
#define NULL 0
#endif

#define BM_WORD 0x4D42


typedef struct tagBitmapHeader
{
	//WORD bfType; //Constant = 0x424D or “BM”
	DWORD bfSize;
	WORD bfReserved1;
	WORD bfReserved2;
	DWORD bfOffBits;
	
	DWORD biSize;
	LONG biWidth;
	LONG biHeight;
	WORD biPlanes; //Constant = 1
	WORD biBitCount;
	DWORD  biCompression;
	DWORD  biSizeImage;
	LONG  biXPelsPerMeter;
	LONG  biYPelsPerMeter;
	DWORD  biClrUsed;
	DWORD  biClrImportant;
}BITMAPHEADER;

typedef struct tagRGBQUAD
{
	BYTE rgbBlue;
	BYTE rgbGreen;
	BYTE rgbRed;
	BYTE rgbReserved;
}RGBQUAD;

typedef struct tagC3RGB
{
	BYTE red;
	BYTE green;
	BYTE blue;
}C3RGB;

typedef struct tagBufferDims
{
	int width;
	int height;
	int depth;
}BUFFERDIMS;

extern void printType(const int t);

extern void ShowBitmapInfo(BITMAPHEADER* pBitmapHeader);

extern BOOL OpenBitmapFile(const char* szFileName, FILE** ppFile);

extern BOOL ReadHeaderInfo(FILE* pFile, BITMAPHEADER* pBitmapHeader, BOOL dumpInfo);

extern BOOL ReadImageData(FILE* pFile, BITMAPHEADER* pBitmapHeader, BYTE** ppBuffer, BUFFERDIMS* pBufferDims);

extern BOOL ReadBitmapFile(const char* szFileName, BYTE** ppBuffer, BUFFERDIMS* pBufferDims, BOOL dumpInfo);

#endif


实现文件Bitmapper.c

/*
 * Bitmapper.c
 *
 *  Created on: 2014年12月18日
 *      Author: fengyhack
 */
#include "Bitmapper.h"

void printType(const int t)
{
	printf("\tCompress type: ");
	switch (t)
	{
	case 0:
		printf("UNCOMPRESSED");
		break;
	case 1:
		printf("BI_RLE8");
		break;
	case 2:
		printf("BI_RLE4");
		break;
	default:
		break;
	}
	printf("\n");
}

void ShowBitmapInfo(BITMAPHEADER* pBitmapHeader)
{
	printf("\n------------------- INFORMATION -------------------\n");
	printf("\tFile volume : %d Bytes\n", pBitmapHeader->bfSize);
	printf("\tContent volume : %d Bytes\n", pBitmapHeader->biSizeImage);
	printf("\tImage size : %d*%d (pixel)\n", pBitmapHeader->biWidth, pBitmapHeader->biHeight);
	printf("\tNumber of color used: %d\n", pBitmapHeader->biClrUsed);
	printf("\tNumber of bit per pixel: %d\n", pBitmapHeader->biBitCount);
	//printf("Compress type: %d\n", pBitmapHeader->biCompression);
	printType(pBitmapHeader->biCompression);
	printf("----------------------------------------------------\n");
}

BOOL OpenBitmapFile(const char* szFileName, FILE** ppFile)
{
	*ppFile = fopen(szFileName, "rb");
	if (*ppFile == NULL)
	{
		printf(">>>ERROR:\nFailed to open file %s\n", szFileName);
		return FALSE;
	}
	return TRUE;
}

BOOL ReadHeaderInfo(FILE* pFile, BITMAPHEADER* pBitmapHeader, BOOL dumpInfo)
{
	WORD bfType;
	fread(&bfType, sizeof(WORD), 1, pFile);
	//if ( BM_WORD != (pBitmapHeader->bfType) )
	if (BM_WORD!=bfType)
	{
		printf(">>>INVALID:\nThis is not a valid bitmap.\n");
		return FALSE;
	}

	fread(pBitmapHeader, sizeof(BITMAPHEADER), 1, pFile);
	if (dumpInfo)
	{
		ShowBitmapInfo(pBitmapHeader);
	}

	return TRUE;
}

BOOL ReadImageData(FILE* pFile, BITMAPHEADER* pBitmapHeader, BYTE** ppBuffer, BUFFERDIMS* pBufferDims)
{
	pBufferDims->width = pBitmapHeader->biWidth;
	pBufferDims->height = pBitmapHeader->biHeight;
	pBufferDims->depth = (pBitmapHeader->biBitCount) >> 3;
	if (pBufferDims->depth == 1)
	{
		printf("This is a grayscale image\n");
	}
	else if (pBufferDims->depth == 3)
	{
		printf("This is a RGB colored image\n");
	}
	else
	{
		printf(">>>INVALID:\nThis is not a 8/24 bit image.\n");
		return FALSE;
	}

	if (pBufferDims->depth == 1)
	{
		RGBQUAD palette[256];
		int i;
		for (/*int */i = 0; i<pBitmapHeader->biClrUsed; ++i)
		{
			fread(&palette[i], sizeof(RGBQUAD), 1, pFile);
		}
	}

	const int nLineBytes = ((pBitmapHeader->biWidth)* (pBitmapHeader->biBitCount) + 31) / 8;
	const size_t bufferSize = nLineBytes*(pBitmapHeader->biHeight);
	printf("Trying to allocate %d bytes memory...",bufferSize);
	*ppBuffer = (BYTE*)malloc(bufferSize);
	if (*ppBuffer == NULL)
	{
		printf("Failed.\n");
		printf(">>>ERROR:\nFailed to allocate memory.\n");
		return FALSE;
	}

	printf("OK.\nPrepare to load image data...\n");
	fread(*ppBuffer, bufferSize, 1, pFile);
	printf("Image data loaded.\n");

	return TRUE;
}

BOOL ReadBitmapFile(const char* szFileName, BYTE** ppBuffer, BUFFERDIMS* pBufferDims, BOOL dumpInfo)
{
	FILE* pFile = NULL;
	BITMAPHEADER bmh;
	//memset(&bmh, 0, sizeof(BITMAPHEADER));
	BOOL status = FALSE;

	if(OpenBitmapFile(szFileName, &pFile))
	{
		if(ReadHeaderInfo(pFile, &bmh,dumpInfo))
		{
			status=ReadImageData(pFile, &bmh, ppBuffer,pBufferDims);
			if (status)
			{
				fclose(pFile);
			}
		}
	}

	return status;
}


其中

typedef struct tagBitmapHeader
{
	//WORD bfType; //Constant = 0x424D or “BM”
	DWORD bfSize;
	WORD bfReserved1;
	WORD bfReserved2;
	DWORD bfOffBits;
	
	DWORD biSize;
	LONG biWidth;
	LONG biHeight;
	WORD biPlanes; //Constant = 1
	WORD biBitCount;
	DWORD  biCompression;
	DWORD  biSizeImage;
	LONG  biXPelsPerMeter;
	LONG  biYPelsPerMeter;
	DWORD  biClrUsed;
	DWORD  biClrImportant;
}BITMAPHEADER;

定义的Bitmap Header结构是Bitmap File Header 和Bitmap Info Header的综合

但是其中第一个WORD被注释掉了,至于为何,请继续阅读。

如果我们查看一幅位图(Bitmap)的ASCII编码内容,可能会是如下这个样子

    

   


因为我们将其二进制以ASCII方式查看,所以有一些是不可打印字符。

但是这些数据都有一个共同特点,就是开头两个字节(也就是一个WORD)都是“BM”

因此,为了判断一个文件是否Bitmap,首先读取开头的一个WORD,如果不是“BM”那它一定不是Bitmap


开头2个字节是公共的“BM”标识


接下来的12个字节依次如下

DWORD bfSize;  // 文件大小(除了位图数据,还有文件属性等信息)
WORD bfReserved1;  // 保留
WORD bfReserved2;  // 保留
DWORD bfOffBits;    // 偏移量

其中,文件大小就是整个文件占用的字节数;

偏移量是指从文件开头开始,往后移动相应字节数就到达图像像素数据内容存放的首地址


再接下来的40个字节依次如下

DWORD biSize;  // 位图存储占用的字节数
LONG biWidth;   // 宽度(像素)
LONG biHeight;  // 高度(像素)
WORD biPlanes;  //平面数,常量=1
WORD biBitCount; // 每个像素用多少个bit来表示,常见的有8bit,24bit等
DWORD  biCompression; // 压缩类型,0未压缩,1为RLE8,2为RLE4
DWORD  biSizeImage;  // 这个不一定等于宽*高*单个像素占用,具体原因见后文
LONG  biXPelsPerMeter; // 水平方向分辨率 (每米多少个像素)
LONG  biYPelsPerMeter; // 垂直方向分辨率 (每米多少个像素)
DWORD  biClrUsed;  // 使用的颜色数 (仅灰度图使用,RGB设置为0)
DWORD  biClrImportant;  // 重要的颜色数


位图数据存储时遵从“4字节对齐”原则:

图像数据按行存储时,一行从头到尾存放,如果这一行恰好凑成4字节的整数倍就正好

否则在末尾填充相应字节,例如一行存储占用401字节,那么就需要填充3个字节,如果是399字节,就需要填充1个字节,等等

这个原则是为了“存取迅速”而设定的


根据这个原则,如果一幅图像数据当中,每一行占用字节数不是4的整数倍,那么在存储时会有填充,

因此上述的biSizeImage可能比“宽*高*单个像素占用”要大

如果图像像素数据中每一行恰好是4的整数倍,那么biSizeImage就和“宽*高*单个像素占用”相等


在读取了BITMAPHEADER之后,就相当于获取了关于这个位图的基本信息,然后根据这些信息,

在内存中开辟存储空间,读取数据并惊醒处理。


实际测试发现

如果将BITMAPHEADER改为下述的

typedef struct tagBitmapHeader
{
	WORD bfType;
	DWORD bfSize;
	WORD bfReserved1;
	WORD bfReserved2;
	DWORD bfOffBits;
	
	DWORD biSize;
	LONG biWidth;
	LONG biHeight;
	WORD biPlanes; //Constant = 1
	WORD biBitCount;
	DWORD  biCompression;
	DWORD  biSizeImage;
	LONG  biXPelsPerMeter;
	LONG  biYPelsPerMeter;
	DWORD  biClrUsed;
	DWORD  biClrImportant;
}BITMAPHEADER;

然后如此判断

	//BITMAPHEADER* pBitmapHeader = ...
	fread(pBitmapHeader, sizeof(BITMAPHEADER), 1, pFile);
	if (BM_WORD != (pBitmapHeader->bfType))
	{
		//...
	}


理论上似乎行得通。但实际上总是出错


因此最后改成本文开头附上的代码,将第一个DWORD bfType分离出来

typedef struct tagBitmapHeader
{
	DWORD bfSize;
	WORD bfReserved1;
	WORD bfReserved2;
	DWORD bfOffBits;
	
	DWORD biSize;
	LONG biWidth;
	LONG biHeight;
	WORD biPlanes; //Constant = 1
	WORD biBitCount;
	DWORD  biCompression;
	DWORD  biSizeImage;
	LONG  biXPelsPerMeter;
	LONG  biYPelsPerMeter;
	DWORD  biClrUsed;
	DWORD  biClrImportant;
}BITMAPHEADER;

分离出来的bfType,执行如下判断(这是第一步操作)

	WORD bfType;
	fread(&bfType, sizeof(WORD), 1, pFile);
	if (BM_WORD!=bfType)
	{
		printf(">>>INVALID:\nThis is not a valid bitmap.\n");
		return FALSE;
	}
然后再读取BITMAPHEADER数据,这种方式实测可行

最后附上一张测试的截图



本文相关源码(包含在测试用例中)可在GitHUb上找到

https://github.com/fengyhack/CodeSnippet/tree/Bitmapper


本文原创,博文原始地址

http://blog.csdn.net/fengyhack/article/details/42103519


转载于:https://www.cnblogs.com/fengyhack/p/10603597.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值