浏览量:2,022

八叉树颜色量化、BMP、TGA文件解析

下载内容:

(1)       C++程序:实现了八叉树颜色量化,写BMP文件和读BMP文件,写TGA文件和读TGA文件三个功能;

(2)       对BMP文件格式和TGA文件格式较完整的叙述的英文文档  

(3)       下载地址:https://github.com/twinklingstar20/octree-bmp-tga/

下载说明:

在写BMP文件时,使用到八叉树颜色量化的方法。而BMP文件格式的解析,从网上可以找到很多这样的博客,比如CSDN上的博客【1】,这里不再描述BMP文件结构。 这里特别说明一点是在代码的实现中,对于每个像素用15bit或者16bit表示的情况。常规的方法,解析出来的RGB颜色值是5位的,把它左移3位就可以。采用参考【2】中的方法,如下所示,在左移3位后,同时在尾部加上一些附加位。具体的说明可以参见相应的博客。

result.r = (result.r << 3) | (result.r >> 2);
result.g = (result.g << 3) | (result.g >> 2);
result.b = (result.b << 3) | (result.b >> 2);

对TGA文件格式的解析,可以参见本网站上的博客《TGA文件格式解析》。

 对八叉树颜色量化方法,可以参见本网站上的博客《八叉树颜色量化》。

在实现读取BMP和TGA文件的功能时,通过了【3】【4】中所有样例的测试。

代码中主要的三个类,如下所示:

颜色量化类

/*	
/brief 颜色量化类

通过八叉树算法来实现颜色量化,给定一个允许的颜色数上限MaxColors,量化后的颜色数一定要小于这个值
可以创建量化后调色板,使用原始颜色可以快速的检索出对应调色板的索引值
*/

class SrColorQuant
{
protected:

#pragma pack(push)
#pragma pack(1)
	typedef struct  _OctreeNode
	{
		bool			blIsLeaf;							// TRUE if node has no children
		unsigned char	inColorIndex;						// Index of the pallette
		unsigned int	uiPixelCount;						// Number of pixels represented by this leaf
		unsigned int	uiRedSum;							// Sum of red components
		unsigned int	uiGreenSum;							// Sum of green components
		unsigned int	uiBlueSum;							// Sum of blue components
		_OctreeNode*	ptrChild[8];						// Pointers to child nodes
		_OctreeNode*	ptrNext;							// Pointer to next reducible node
	}OctreeNode;
#pragma  pack(pop)

public:
	SrColorQuant( );
	~SrColorQuant();
	/*
	\brief 创建八叉树
	\param[in] btPtrRgbData	需要量化的颜色数组,每个颜色占三个字节,依次为红、绿、蓝,每种颜色分量以1个字节保存。
	\param[in] inPixelCount 需要量化的颜色个数
	\param[in] nMaxColors 规定量化后最大允许的颜色数,它的值必需[1,256]范围内。
	\return 采用八叉树量化的颜色数个数。如果返回0,则八叉树创建失败
	*/
	int		buildOctree( unsigned char* btPtrRgbData,int inPixelCount,int nMaxColors);
	/*
	\brief 根据颜色RGB,从已创建出的八叉树中获得相近颜色的索引
	\param[in] (byRed,byGreen,byBlue) RGB颜色值
	*/
	unsigned char  indexOctree(unsigned char byRed,unsigned char byGreen,unsigned char byBlue)const;
	/*
	\brief 叶节点的最大计数值
	*/
	int		getMaxPixelCount() const;
	/*
	\brief  叶节点的数量,即量化后的颜色种数
	*/
	int		getLeafNodeCount()const;
	/*
	\brief 如果八叉树为空,返回true
	*/
	bool	isEmpty()const;
	/*
	\brief 获得量化后的调色板,每个颜色占三个字节,依次为红、绿、蓝,每种颜色分量以1个字节保存。
	\param[in] ptrColorPal 不能为空,内存需要提前分配,至少有getLeafNodeCount()*3大小的内存空间。
	*/
	void	getColorPallette(unsigned char* ptrColorPal )const;

protected:
	void		getColorPallette(OctreeNode*ptrTree, unsigned char* ptrColorPal)const;
	OctreeNode* createNode (int inLevel);
	/*
	\brief	合并八叉树节点,降低叶节点数
	*/
	void		reduceTree ();
	/*
	\brief 递归的往八叉树中插入颜色值
	\param[in] inLevel表示当前插入的层数,初始值是0,每递归一层,值加1 
	*/
	bool		addColor(OctreeNode*& ptrTreeNode,unsigned char byRed,unsigned char byGreen,unsigned char byBlue,int inLevel);
	/*
	\brief 计算每个叶节点的颜色值同时把颜色计数值设置为1.
	*/
	void		setColorIndex(OctreeNode* ptrTree,int& inIndex);
	/*
	\brief	析构八叉树内存
	*/
	void		freeOctree(OctreeNode*& ocPtrTree);
	/*
	\brief 清空八叉树
	*/
	void		empty();

	unsigned int	m_inMaxPixelCount;
	int				m_inLeafCount;	
	OctreeNode*		m_ptrReducibleNodes[9];
	OctreeNode*		m_ptrTree;
};

BMP文件的解析类

/*
\brief BMP文件的读写解析类

一个BMP类对象只能进行BMP文件的读取或者写入。

读BMP文件:解析BMP文件必需满足如下条件:
		   (1)支持像素比特数是1,4,8,16,24,32,图像压缩类型为BI_RGB
		   (2)压缩类型为BI_RLE4和BI_RLE8,对应的像素比特数分别是4和8
		   (3)BMP文件的宽和高必需大于0,biPlanes必需是1,必需是"BM"类型
写BMP文件:支持写入像素比特数是1,4,8,16,24,图像压缩类型为BI_RGB的BMP文件
*/

class SrImageBmp: public SrImage
{

protected:

	//一些BMP文件中文件头和文件信息头即使有错误也不影响正确使用BMP中的图像信息
	//是否检测文件头中位图数据的起始位置
#define  SR_IMAGE_BMP_CHECK_OFFBITS 0
	//是否检测文件头中文件大小
#define  SR_IMAGE_BMP_CHECK_FILESIZE 0

#pragma pack(push) 
#pragma pack(1)
	/*
		BMP文件由四部分组成:BMP文件头(14字节)、位图信息头(40字节)、颜色表、位图数据。
	*/

	/*
		BMP文件头(14字节)
		BMP文件头数据结构含有BMP文件的类型、文件大小和位图起始位置等信息。 
	*/
	typedef struct tagBITMAPFILEHEADER
	{
		WORD bfType;				// 位图文件的类型,必须为BMP(0-1字节)
		DWORD bfSize;				// 位图文件的大小,以字节为单位(2-5字节)
		WORD bfReserved1;			// 位图文件保留字,必须为0(6-7字节)
		WORD bfReserved2;			// 位图文件保留字,必须为0(8-9字节)
		DWORD bfOffBits;			// 位图数据的起始位置,以相对于位图文件头的偏移量表示,以字节为单位(10-13字节)
	} BITMAPFILEHEADER;

	/*
		位图信息头(40字节)
		BMP位图信息头数据用于说明位图的尺寸等信息。
	*/
	typedef struct tagBITMAPINFOHEADER
	{
		DWORD biSize;				// 本结构所占用字节数,通是40个字节(14-17字节)
		LONG biWidth;				// 位图的宽度,以像素为单位(18-21字节)
		LONG biHeight;				// 位图的高度,以像素为单位(22-25字节)
		WORD biPlanes;				// 目标设备的级别,必须为1(26-27字节)
		WORD biBitCount;			// 每个像素所需的位数,必须是1(双色)、(28-29字节)、4(16色)、8(256色)或24(真彩色)之一
		DWORD biCompression;		// 位图压缩类型,必须是 0(不压缩),(30-33字节)
									// 1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一
		DWORD biSizeImage;			// 位图的大小,以字节为单位(34-37字节)
		LONG biXPelsPerMeter;		// 位图水平分辨率,每米像素数(38-41字节),解析中未使用该信息
		LONG biYPelsPerMeter;		// 位图垂直分辨率,每米像素数(42-45字节),解析中未使用该信息
		DWORD biClrUsed;			// 位图实际使用的颜色索引表中的颜色数(46-49字节)
		DWORD biClrImportant;		// 位图显示过程中重要的颜色数(50-53字节),解析中未使用该信息
	}BITMAPINFOHEADER;

	/*
		颜色表 
		颜色表用于说明位图中的颜色,它有若干个表项,每一个表项是一个RGBQUAD类型的结构,定义一种颜色。
	*/
	typedef struct tagRGBQUAD 
	{
		BYTE rgbBlue;			// 蓝色的亮度(值范围为0-255)
		BYTE rgbGreen;			// 绿色的亮度(值范围为0-255)
		BYTE rgbRed;			// 红色的亮度(值范围为0-255)
		BYTE rgbReserved;		// 保留,必须为0
	}RGBQUAD;
	/*
		位图信息头和颜色表组成位图信息
	*/
	typedef struct tagBITMAPINFO
	{
		BITMAPINFOHEADER bmiHeader;
		RGBQUAD bmiColors[1];
	}BITMAPINFO;
#pragma pack(pop)

public:
	SrImageBmp(int isReadOnly);
	~SrImageBmp();
	/*
	\brief	从BMP文件中读取数据,并且返回数据
	\param[in] chPtrImageFile 读取的BMP文件名
	\param[out] ptrOutData 如果inRGBType等于IMAGE_RGB,则返回的RGB颜色数据,每个颜色占三个字节,依次为红、绿、蓝,每种颜色分量以1个字节保存;
						   如果inRGBType等于IMAGE_RGBA,则返回的RGBA颜色数据,每个颜色占四个字节,依次为红、绿、蓝,Alpha,每种颜色分量以1个字节保存;
						   不可以析构数据ptrOutDatak中的内存,它由BMP对象进行管理。
	\param[out] lgPixelCount 返回的RGB颜色数
	\param[out] inRGBType 只能是IMAGE_RGBA和IMAGE_RGB中的一个值,表示返回的数据格式是RGBA还是RGB
	\return true,读文件成功;false,读文件失败,通过getErrorId()方法,获得错误代号。
	*/
	bool	readFile(const char* chPtrImageFile,unsigned char*& ptrOutData , int& lgPixelCount,int& inRGBType);
	/*
	\brief	将RGB颜色数据保存进BMP对象中,以便后续写文件操作。
	\param[in] ucPtrRgbData RGB颜色数据,要保存进BMP对象中的数据,依次为红、绿、蓝,每种颜色分量以1个字节保存
	\param[in] inWidth 指定写入BMP对象的宽,必需大于0
	\param[in] inHeight 指定写入BMP对象的高,必需大于0
	\param[in] usBitCount 指定BMP对象中每种颜色占的位数,BMP文件只支持1,4,8,16,24,32这6个数值
	\param[in] ulCompression 这里只支持BI_RGB类型的写入文件操作
	\return true,装载文件成功;false,装载文件失败,通过getErrorId()方法,获得错误代号。
	*/
	bool	loadImageData(unsigned char* ucPtrRgbData ,long inWidth,long inHeight,unsigned short usBitCount=8 );
	/*
	\brief	将保存在BMP对象中的数据写入BMP文件中
	\param[in] chPtrImageFile 写入的BMP文件名
	\return true,写文件成功;false,写文件失败,通过getErrorId()方法,获得错误代号。
	*/
	bool	writeFile(const char* chPtrImageFile);

	virtual bool	isValid()const;
	virtual int		getWidth()const;
	virtual int		getHeight()const;
	/*
	\brief	返回BMP对象中的RGB数据或者RGBA数据
	*/
	virtual unsigned char* getImageData()const;

	unsigned long		getFileSize()const;
	/*
	\brief	判断BMP对象中BMP的压缩类型,只能是BI_RGB或者BI_RGBA
	*/
	unsigned long		getCompression()const;
	/*
	\brief	每个像素的比特数
	*/
	unsigned short		getPixelDepth()const;
	/*
	\brief	判断BMP对象中存储的是RGB数据还是RGBA数据
	*/
	bool				getIsRGB()const;

private:
	/*
	\brief	析构BMP对象中的所有数据
	*/
	void	empty();
	/*
	\brief	计算BMP文件一行的字节数
	*/
	long	getLineBytes(int imgWidth,int bitCount)const;
	/*
	\brief	判断读取的BMP文件格式是否正确
	*/
	bool	checkReadFileFormat(FILE* flFileHandle);
	bool	readUncompression(FILE* filePtrImage,unsigned char*& ptrOutData);
	bool	decodeRLE(FILE* flPtrImage,BYTE*& btPtrImageData);
	/*
	\brief	从文件中读取BMP文件头和文件信息头
	*/
	bool	readHeader(FILE* flPtrImage);
	bool	readColorMap(FILE* flPtrImage);

	bool	decodeFile(BYTE* ptrImageData,unsigned char*& rgbPtrOutData);

	bool	initHeader(long inWidth,long inHeight,unsigned short usBitCount);
	bool	writeHeader(FILE* flPtrImage);
	bool	writeColorMap(FILE* filePtrImage,SrColorQuant& colorQuant);
	bool	writeImageData(FILE* flPtrFile,SrColorQuant& colorQuant);
	bool	writeBinary(FILE* filePtrImage);
	bool	writeColorMapImage(FILE* filePtrImage,const SrColorQuant& colorQuant);
	bool	writeNoColorMapImage(FILE* filePtrImage);
	bool	checkWriteFileFormat();

public:
#ifdef _DEBUG
#ifdef _CONSOLE
	void printFileHeader(BITMAPFILEHEADER *bmfh);

	void printFileHeader(BITMAPINFOHEADER *bmih);
#endif
#endif

private:

	BITMAPINFOHEADER*	m_ptrInfoHeader;
	BITMAPFILEHEADER*	m_ptrFileHeader;
	RGBQUAD*			m_ptrColorMap;		
	BYTE*				m_ptrImageData;

	int					m_inIsReadOnly;

};

TGA文件的解析类

/*
\brief TGA文件的读写解析类

一个TGA类对象只能进行TGA文件的读取或者写入

读TGA文件:解析TGA文件必需满足如下条件:
		   (1)要求头文件中的宽和高必需是正数;
		   (2)位深度可以是8,15,16,24,32;
		   (3)如果位深度是15,16,24,32,则不使用颜色表;
		   (4)如果位深度是8,如果不采用颜色表,则只能解析黑白图,即图像类型是TGA_UN_BLACK_WHITE或者TGA_RLE_BLACK_WHITE;
		   (5)如果使用到颜色表,每个表项所占的大小必需是15,16,24,32中任意一个值
		   (6)tgaXOrigin,tgaYOrigin必需是0
		    (7)tgaImageDesc必需是0

写TGA文件:只实现了保存24bit真彩无压缩的TGA格式
*/

class SrImageTga:public SrImage
{
protected:
//定义了7种文件类型,TGA_IMAGE_NO_DATA文件类型不解析
#define		TGA_IMAGE_NO_DATA		0x00
#define		TGA_UN_COLOR_MAP		0x01
#define		TGA_UN_TRUE_COLOR		0x02
#define		TGA_UN_BLACK_WHITE		0x03
#define		TGA_RLE_COLOR_MAP		0x09
#define		TGA_RLE_TRUE_COLOR		0x0A
#define		TGA_RLE_BLACK_WHITE		0x0B

#define  SR_IMAGE_TGA_CHECK_ORIGIN		0
#define  SR_IMAGE_TGA_CHECK_IMAGEDESC	0

#pragma pack(push)
#pragma pack(1)
	//TGA文件头
	typedef struct 
	{
		BYTE tgaIdLength;			//图像信息字段长度
		BYTE tgaColorMapType;		//颜色表类型
		BYTE tgaImageType;			//图像类型
		WORD tgaFirstEnIndex;		//颜色表首地址
		WORD tgaColorMapLen;		//颜色表长度
		BYTE tgaColorMapEnSize;		//颜色表表项大小
		WORD tgaXOrigin;			//图像X位置的起始位置
		WORD tgaYOrigin;			//图像Y位置的起始位置
		WORD tgaWidth;				//图像宽度
		WORD tgaHeight;				//图像高度
		BYTE tgaPixelDepth;			//像素深度
		BYTE tgaImageDesc;			//图像描述符
	}TGAFILEHEADER;

	//TGA文件注脚
	typedef struct  
	{
		LONG tgaExtOffset;			//拓展区域偏移量
		LONG tgaDevOffset;			//开发者区域偏移量
		BYTE tgaSigniture[16];		//签名
		BYTE tgaAscii;				//ASCII码
		BYTE tgaTerminator;			//二进制数0x00
	}TGAFILEFOOTER;

	//图像、颜色表数据
	typedef struct  
	{
		BYTE*	tgaImageId;			//图像信息字段
		BYTE*	tgaColorMap;		//颜色表数据
		BYTE*	tgaImageData;		//图像数据
	}TGAIMAGECOLORDATA;

	//存储除TGA文件头以外的数据,称为文件结构数据
	typedef struct  
	{
		LONG				tgaFileSize;		//文件大小
		TGAIMAGECOLORDATA	tgaImageColorData;	//图像、颜色表数据
		BYTE*				tgaPtrDevData;		//开发者区域数据
		BYTE*				tgaPtrExtData;		//拓展区域数据
	}TGASTRUCTUREDATA;
#pragma pack(pop)

public:
	SrImageTga(int isReadOnly);
	~SrImageTga();
	/*
	\brief	从TGA文件中读取数据,并且返回数据
	\param[in] chPtrFileName 读取的TGA文件名
	\param[out] btPtrImageData 如果inRGBType等于IMAGE_RGB,则返回的RGB颜色数据,每个颜色占三个字节,依次为红、绿、蓝,每种颜色分量以1个字节保存;
							   如果inRGBType等于IMAGE_RGBA,则返回的RGBA颜色数据,每个颜色占四个字节,依次为红、绿、蓝,Alpha,每种颜色分量以1个字节保存;
							   不可以析构数据ptrOutDatak中的内存,它由BMP对象进行管理。
	\param[out] lgPixelCount 返回的RGB颜色数
	\param[out] inRGBType	只能是IMAGE_RGBA和IMAGE_RGB中的一个值,表示返回的数据格式是RGBA还是RGB
	\return true,读文件成功;false,读文件失败,通过getErrorId()方法,获得错误代号。
	*/
	bool readFile(const char* chPtrFileName,BYTE*& btPtrImageData,int& lgPixelCount,int& inRGBType);
	/*
	\brief	将RGB颜色数据保存进TGA对象中,以便后续写文件操作。
	\param[in] ucPtrRgbData RGB颜色数据,要保存进TGA对象中的数据,依次为红、绿、蓝,每种颜色分量以1个字节保存
	\param[in] inWidth 指定写入TGA对象的宽,必需大于0
	\param[in] inHeight 指定写入TGA对象的高,必需大于0
	\return true,装载文件成功;false,装载文件失败,通过getErrorId()方法,获得错误代号。
	*/
	bool loadImageData(unsigned char* ucPtrRgbData ,unsigned short inWidth,unsigned short inHeight);
	/*
	\brief 将存储在TGA文件对象中的数据保存到TGA文件中,只保存24bit真彩无压缩的TGA格式
	\param[in] chPtrImageFile 写入的文件名
	*/
	bool writeFile(const char* chPtrImageFile)const;

	virtual bool	isValid()const;
	virtual int		getWidth()const;
	virtual int		getHeight()const;
	virtual unsigned char* getImageData()const;
	/*
	\brief	判断TGA对象中存储的是RGB数据还是RGBA数据
	*/
	bool			getIsRGB()const;
	/*
	\brief	判断TGA对象是采用原始的TGA文件格式,还是新的TGA文件格式
	*/
	bool			isNewTgaFormat()const;
	/*
	\brief	每个像素的比特数
	*/
	unsigned char	getPixelDepth()const;
	/*
	\brief	TGA文件类型,总共包括6种文件类型
	*/
	unsigned char	getImageType()const;
	/*
	\brief	判断TGA是否使用了颜色表
	*/
	bool			getUseMapType()const;
	/*
	\brief	颜色表表项的位数,
	\return 0,表示非使用颜色表;否则,是15,16,24,32中的某个值
	*/
	int				getGetMapEntrySize()const;
	/*
	\brief	返回图像信息字段
	\param[out] ptrImageInfo 返回图像信息字段的内容,注意:该块内存不规TGA管理,使用完后,需要释放,否则会造成内存泄漏
	\return 图像信息字段的长度
	*/
	int				getImageInfo(unsigned char*& ptrImageInfo)const;

private:
	bool mallocMemory();
	void deallocMemory();

	bool readFileFooter(FILE* flPtrFile);
	bool readFileDevExt(FILE * flPtrFile);
	bool readUncompressed(FILE*flPtrFile ,BYTE* btPtrOutData);
	bool readCompressed(FILE*flPtrFile ,BYTE* btPtrOutData);
	bool readCompressedMap(FILE*flPtrFile ,BYTE* btPtrColorMap,BYTE* btPtrOutData);
	bool readUncompressedMap(FILE*flPtrFile ,BYTE* btPtrColorMap,BYTE* btPtrOutData);
	bool checkFileFormat()const;
	bool readFileImageData(FILE* flPtrFile, BYTE*& btPtrOutData, int& inRGBType );

private:
	TGAFILEHEADER*		m_ptrFileHeader;
	TGAFILEFOOTER*		m_ptrFileFooter;
	TGASTRUCTUREDATA*	m_ptrStruInfo;

	int					m_inIsReadOnly;
	BYTE*				m_ptrImageData;
};

参考

【1】http://blog.csdn.net/o_sun_o/article/details/8351037

【2】http://www.ryanjuckett.com/programming/graphics/26-parsing-colors-in-a-tga-file

【3】http://www.fileformat.info/format/bmp/sample/

【4】http://www.fileformat.info/format/tga/sample/

spacer

Leave a reply