上个月开始和同学一块在做类似CS的射击游戏demo,要自己实现游戏里的简单2DOverlay和文本显示(不用具体GUI了,菜单什么的再用CEGUI),由于我这人做事慢,所以就去实现字体这种小模块~0~(学末总是很难静下心编程唉~~),本来以为网上这类资源挺多的,搜到的无非是NeHe的openGL+ft2,还有一位仁兄模仿Ogre写的一个代码(里面有些问题),多不是很系统,自己认真花了3,4天模仿OgreFont实现了一个简单的英文字体在dx 9下使用ID3DXSprite接口进行渲染,然后照着clayman和hyzboy的提示修改成了支持中文的动态调频写入纹理那样(不过没做测试哦~~下下周得去上海2K面试,我就将就用了~)问题应该还有很多,而且应该做成考虑时间那样(LRU),以后再说吧。。。
整个流程:
最后把代码贴下面吧,希望对大家有帮助。
/************************************************************************\
This is a fucking
______ ___ _ _
| ___| / _ \ | | | |
| |_ / /_\ \| |__ | | ___
| _| | _ || '_ \ | | / _ \
| | | | | || |_) || || __/
\_| \_| |_/|_.__/ |_| \___| 's free file
filename: Font.h
created: 2010/07/30
creator: 承天一
purpose: Freetype字体类
*************************************************************************/
#ifndef __FONT_H__
#define __FONT_H__
#include <map>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
using namespace std;
namespace Fable
{
class Font : public NedAlloc
{
public:
typedef unsigned int CodePoint;
typedef FloatRect UVRect;
/// Information about the position and size of a glyph in a texture
struct GlyphInfo
{
public:
CodePoint codePoint; //字符的unicode码
UVRect uvRect; //纹理区域
float aspectRatio;
USHORT useCount; //字符的使用次数
UINT l;
UINT m;
public:
GlyphInfo(CodePoint _code, const UVRect& _rect, float _aspect, UINT _l, UINT _m)
: codePoint(_code), uvRect(_rect), aspectRatio(_aspect), useCount(0), l(_l), m(_m)
{
}
};
/// A range of code points, inclusive on both ends
typedef std::pair<CodePoint, CodePoint> CodePointRange;
typedef vector<CodePointRange> CodePointRangeList;
protected:
/// Map from unicode code point to texture coordinates
typedef map<CodePoint, GlyphInfo> CodePointMap;
CodePointMap mCodePointMap;
/// Range of code points to generate glyphs for (truetype only)
CodePointRangeList mCodePointRangeList;
public:
Font();
~Font();
void load(const std::string& name);
void unLoad();
void addCodePointRange(const CodePointRange& range)
{
mCodePointRangeList.push_back(range);
}
/** Clear the list of code point ranges.
*/
void clearCodePointRanges()
{
mCodePointRangeList.clear();
}
/** Get a const reference to the list of code point ranges to be used to
generate glyphs from a truetype font.
*/
const CodePointRangeList& getCodePointRangeList() const
{
return mCodePointRangeList;
}
protected:
/// Size of the truetype font, in points
float mTtfSize;
/// Resolution (dpi) of truetype font
unsigned int mTtfResolution;
/// Max distance to baseline of this (truetype) font
int mTtfMaxBearingY;
/// for TRUE_TYPE font only
bool mAntialiasColour;
IDirect3DTexture9* mTexture;
UINT mWidth;
UINT mHeight;
std::string mFontName;
/// 纹理上使用区域还剩的个数
UINT mLeftBlankNum;
UINT mMaxCharNum;
uchar* mImageData;
FT_Library mFtLibrary;
FT_Face mFtFace;
UINT mPixelBytes;
UINT mCharDataWidth;
UINT mMaxCharSize;
UINT mDataSize;
int mMaxHeight;
int mMaxWidth;
float mTextureAspect;
UINT mCharSpacer;
UINT mImage_m;
UINT mImage_l;
public:
UINT getTextureWidth() const { return mWidth;}
UINT getTextureHeight() const { return mHeight;}
inline const UVRect& getGlyphTexCoords(CodePoint id) const
{
CodePointMap::const_iterator i = mCodePointMap.find(id);
if (i != mCodePointMap.end())
{
return i->second.uvRect;
}
else
{
static UVRect nullRect(0.0, 0.0, 0.0, 0.0);
return nullRect;
}
}
/** Sets the texture coordinates of a glyph.
@remarks
You only need to call this if you're setting up a font loaded from a texture manually.
@note
Also sets the aspect ratio (width / height) of this character. textureAspect
is the width/height of the texture (may be non-square)
*/
inline void setGlyphTexCoords(CodePoint id, UINT u1Pixel, UINT v1Pixel, UINT u2Pixel, UINT v2Pixel, float textureAspect)
{
float u1 = (float)u1Pixel / (float)mWidth, v1 = (float)v1Pixel / (float)mHeight, u2 = (float)u2Pixel / (float)mWidth, v2 = (float)v2Pixel / (float)mWidth;
CodePointMap::iterator i = mCodePointMap.find(id);
if (i != mCodePointMap.end())
{
i->second.uvRect.left = u1;
i->second.uvRect.top = v1;
i->second.uvRect.right = u2;
i->second.uvRect.bottom = v2;
i->second.aspectRatio = textureAspect * (u2 - u1) / (v2 - v1);
i->second.l = u1Pixel;
i->second.m = v1Pixel;
}
else
{
mCodePointMap.insert(
CodePointMap::value_type(id,
GlyphInfo(id, UVRect(u1, v1, u2, v2),
textureAspect * (u2 - u1) / (v2 - v1), u1Pixel, v1Pixel)));
}
}
/** Gets the aspect ratio (width / height) of this character. */
inline float getGlyphAspectRatio(CodePoint id) const
{
CodePointMap::const_iterator i = mCodePointMap.find(id);
if (i != mCodePointMap.end())
{
return i->second.aspectRatio;
}
else
{
return 1.0;
}
}
/** Sets the aspect ratio (width / height) of this character.
@remarks
You only need to call this if you're setting up a font loaded from a
texture manually.
*/
inline void setGlyphAspectRatio(CodePoint id, float ratio)
{
CodePointMap::iterator i = mCodePointMap.find(id);
if (i != mCodePointMap.end())
{
i->second.aspectRatio = ratio;
}
}
/** Gets the information available for a glyph corresponding to a
given code point, or throws an exception if it doesn't exist;
*/
const GlyphInfo* getGlyphInfo(CodePoint id) const;
LPDIRECT3DTEXTURE9 getFontTexture() const { return mTexture; }
void insertGlyphInfo(CodePoint id);
bool hasBlankInTexture() const
{
return mLeftBlankNum > 0;
}
void renderGlyphIntoTexture(CodePoint id);
CodePoint getLessUseChar();
void removeGlyph(CodePoint id);
};
}
#endif
/************************************************************************\
This is a fucking
______ ___ _ _
| ___| / _ \ | | | |
| |_ / /_\ \| |__ | | ___
| _| | _ || '_ \ | | / _ \
| | | | | || |_) || || __/
\_| \_| |_/|_.__/ |_| \___| 's free file
filename: Font.cpp
created: 2010/07/30
creator: 承天一
purpose: Freetype字体类
*************************************************************************/
#include "stdafx.h"
#include "RenderCore.h"
#include "Font.h"
#include "TextureManager.h"
#include <d3dx9tex.h>
#undef max
#undef min
namespace Fable
{
Font::Font()
:mTtfMaxBearingY(0), mTtfResolution(0), mAntialiasColour(false),
mTexture(0), mLeftBlankNum(0),
mImageData(nullptr), mImage_m(0),mImage_l(0)
{
mWidth = 1024;
mHeight = 1024;
mTtfSize = 20;
mTtfResolution = 96;
}
Font::~Font()
{
unLoad();
}
void Font::load(const std::string& name)
{
mFontName = name;
FT_Library ftLibrary;
//初始化库
if(FT_Init_FreeType(&ftLibrary))
FA_EXCEPT(ERR_FONT, "FreeType初始化失败");
mCharSpacer = 5;
if(FT_New_Face(ftLibrary, name.c_str(), 0, &mFtFace))
FA_EXCEPT(ERR_FONT, "FreeType无法打开ttf文件");
UINT maxFaceNum = mFtFace->num_faces;
FT_F26Dot6 ftSize = (FT_F26Dot6)(mTtfSize * (1 << 6));
if(FT_Set_Char_Size( mFtFace, ftSize, 0, mTtfResolution, mTtfResolution))
FA_EXCEPT(ERR_FONT, "Could not set char size!");
mMaxHeight = 0, mMaxWidth = 0;
if(mCodePointRangeList.empty())
{
mCodePointRangeList.push_back(CodePointRange(33, 166));
mCodePointRangeList.push_back(CodePointRange(19968, 40869));
}
// Calculate maximum width, height and bearing
for (CodePointRangeList::const_iterator r = mCodePointRangeList.begin();
r != mCodePointRangeList.end(); ++r)
{
const CodePointRange& range = *r;
for(CodePoint cp = range.first; cp <= range.second; ++cp)
{
FT_Load_Char( mFtFace, cp, FT_LOAD_RENDER );
if( ( 2 * ( mFtFace->glyph->bitmap.rows << 6 ) - mFtFace->glyph->metrics.horiBearingY ) > mMaxHeight )
mMaxHeight = ( 2 * ( mFtFace->glyph->bitmap.rows << 6 ) - mFtFace->glyph->metrics.horiBearingY );
if( mFtFace->glyph->metrics.horiBearingY > mTtfMaxBearingY )
mTtfMaxBearingY = mFtFace->glyph->metrics.horiBearingY;
if( (mFtFace->glyph->advance.x >> 6 ) + ( mFtFace->glyph->metrics.horiBearingX >> 6 ) > mMaxWidth)
mMaxWidth = (mFtFace->glyph->advance.x >> 6 ) + ( mFtFace->glyph->metrics.horiBearingX >> 6 );
}
}
// We just make a 1024 * 1024 texture, it's enough
mTextureAspect = 1.0f;
mPixelBytes = 2;
mCharDataWidth = (mMaxWidth + mCharSpacer) * mPixelBytes;
mDataSize= mWidth * mHeight * mPixelBytes;
mMaxCharSize = ((mMaxHeight >> 6) + mCharSpacer) * mCharDataWidth;
mMaxCharNum = mDataSize / mMaxCharSize;
mLeftBlankNum = mMaxCharNum;
CON_INFO("Font texture size %d * %d", mWidth, mHeight);
mImageData= FA_NEW_ARRAY_T(uchar, mDataSize);
// Reset content (transparent, white)
for (size_t i = 0; i < mDataSize; i += mPixelBytes)
{
mImageData[i + 0] = 0xFF; // luminance
mImageData[i + 1] = 0x00; // alpha
}
HRESULT hr = 0;
hr = D3DXCreateTexture(
RenderCore::getInstancePtr()->getDevice(),
mWidth,
mHeight,
1,
0,
D3DFMT_A8L8,
D3DPOOL_MANAGED,
&mTexture);
if(FAILED(hr))
{
string msg = DXGetErrorDescriptionA(hr);
FA_EXCEPT(ERR_FONT, "Create font Texture failed: " + msg);
}
}
void Font::unLoad()
{
FA_DELETE_ARRAY_T(mImageData, uchar, mDataSize);
SAFE_RELEASE(mTexture);
FT_Done_FreeType(mFtLibrary);
}
const Font::GlyphInfo* Font::getGlyphInfo(CodePoint id) const
{
CodePointMap::const_iterator i = mCodePointMap.find(id);
if (i == mCodePointMap.end())
{
return nullptr;
}
return &i->second;
}
void Font::renderGlyphIntoTexture(CodePoint id)
{
FT_Error ftResult;
// Load & render glyph
ftResult = FT_Load_Char( mFtFace, id, FT_LOAD_RENDER );
if (ftResult)
{
// problem loading this glyph, continue
CON_INFO("Info: cannot load CodePoint %d", id);
}
FT_Int advance = mFtFace->glyph->advance.x >> 6;
unsigned char* buffer = mFtFace->glyph->bitmap.buffer;
if (!buffer)
{
// Yuck, FT didn't detect this but generated a null pointer!
CON_INFO("Info: Freetype returned null for character %d", id);
}
int y_bearnig = ( mTtfMaxBearingY >> 6 ) - ( mFtFace->glyph->metrics.horiBearingY >> 6 );
int x_bearing = mFtFace->glyph->metrics.horiBearingX >> 6;
for(int j = 0; j < mFtFace->glyph->bitmap.rows; ++j )
{
size_t row = j + mImage_m + y_bearnig;
UCHAR* pDest = &mImageData[(row * mWidth * mPixelBytes) + (mImage_l + x_bearing) * mPixelBytes];
for(int k = 0; k < mFtFace->glyph->bitmap.width; ++k )
{
if (mAntialiasColour)
{
// Use the same greyscale pixel for all components RGBA
*pDest++= *buffer;
}
else
{
// Always white whether 'on' or 'off' pixel, since alpha
// will turn off
*pDest++= 0xFF;
}
// Always use the greyscale value for alpha
*pDest++= *buffer++;
}
}
this->setGlyphTexCoords(id,
mImage_l, // u1
mImage_m, // v1
mImage_l + ( mFtFace->glyph->advance.x >> 6 ), // u2
mImage_m + ( mMaxHeight >> 6 ), // v2
mTextureAspect
);
// Advance a column
mImage_l += (advance + mCharSpacer);
// If at end of row
if( mWidth - 1 < mImage_l + ( advance ) )
{
mImage_m += ( mMaxHeight >> 6 ) + mCharSpacer;
mImage_l = 0;
}
--mLeftBlankNum;
D3DLOCKED_RECT lockedRect;
mTexture->LockRect(0, &lockedRect,0, 0);
//使用类型注意
uchar* TexData = (uchar*)lockedRect.pBits;
for(UINT i = 0; i < mHeight; ++i)
{
for(UINT j = 0; j < mWidth; ++j)
{
//Pitch数据的总长度
int index = i * lockedRect.Pitch / mPixelBytes + j;
TexData[index] = mImageData[index];
}
}
mTexture->UnlockRect(0);
// for test
//#ifdef _DEBUG
// D3DXSaveTextureToFileA("..//media//test.png",D3DXIFF_PNG, mTexture, 0);
//#endif
}
void Font::insertGlyphInfo(CodePoint id)
{
if(!hasBlankInTexture()) //has no space left in texture
{
removeGlyph(getLessUseChar());
}
renderGlyphIntoTexture(id);
}
Font::CodePoint Font::getLessUseChar()
{
CodePointMap::iterator i = mCodePointMap.begin(), iend = mCodePointMap.end(), iless = mCodePointMap.begin();
while(i != iend)
{
if(i->second.useCount < iless->second.useCount)
iless = i;
++i;
}
return iless->second.codePoint;
}
void Font::removeGlyph(CodePoint id)
{
CodePointMap::iterator it = mCodePointMap.find(id);
if(it != mCodePointMap.end())
{
mImage_l = it->second.l;
mImage_m = it->second.m;
mCodePointMap.erase(it);
++mLeftBlankNum;
}
else
{
FA_EXCEPT(ERR_FONT, "Can not find CodePoint to remove in void Font::removeGlyph(CodePoint id)");
}
}
}