/*!
* \file: Exp1.cpp
* \date: 2020/05/29 15:04
* \author: wangqi
* Contact: wangq@southzn.com
*
* \brief:
*
* TODO: long description
*
* \note: 实现了FT库生成字符位图,并上传到GL纹理。
实现了字符位图缓存功能,多个字符图像保存在同一个纹理中。
实现了简单的字体管理框架。
实现了简单的加粗和倾斜效果。
实现了反锯齿开关,并且兼容加粗倾斜效果。
*/
// OpenGL library
#include <gl/glut.h>
// Std misc
#include <map>
#include <vector>
// FreeType library
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_BITMAP_H
#include FT_OUTLINE_H
#ifdef CreateFont
#undef CreateFont
#endif
typedef unsigned char byte;
class CFontManager //字体管理器:管理了一个字体库
{
public:
CFontManager();
~CFontManager();
// 初始化
bool initialize(void);
// 释放
void release(void);
/*!
* \brief: createFont 创建字体
*
* \param: filename 字体文件的路径
* \param: face
* \param: tall 高度
* \param: bold 加粗
* \param: italic 是否斜体
* \param: antialias 是否抗锯齿
* \returns: int
* \author: wangqi
* \date: 2020/06/02 14:16
*
* TODO:
*
*/
int createFont(const char *filename, int face, int tall, bool bold, bool italic, bool antialias);
// 得到字符信息
bool getCharInfo(int font_index, int code, int *wide, int *tall, int *horiBearingX, int *horiBearingY, int *horiAdvance, GLuint *texture, float coords[]);
// 得到字体的高度
int getFontTall(int font_index);
private:
struct glyphMetrics //字形度量
{
int width; //宽度
int height; //长度
int horiBearingX;
int horiBearingY;
int horiAdvance;
//int vertBearingX;
//int vertBearingY;
//int vertAdvance;
};
class CFont //字体
{
public:
CFont();
~CFont();
/*!
* \brief: create 创建字体
*
* \param: library FT库。需要提前FT_Init_FreeType
* \param: filename 字体文件的路径
* \param: face_index Face下标
* \param: tall 高度
* \param: bold 是否加粗
* \param: italic 是否斜体
* \param: antialias 是否抗锯齿
* \returns: bool
* \author: wangqi
* \date: 2020/06/02 14:13
*
* TODO:
*
*/
bool create(FT_Library library, const char *filename, FT_Long face_index,
int tall, bool bold, bool italic, bool antialias);
// 释放
void release(void);
// 得到字符串信息
bool getCharInfo(int code, glyphMetrics *metrics, GLuint *texture, float coords[]);
// 得到字符高度
int getFontTall(void);
private:
// 加载字符
bool loadChar(int code, glyphMetrics *metrics);
class CChar //渲染之后的字符
{
public:
// 设置信息
void setInfo(glyphMetrics *metrics);
// 得到信息
void getInfo(glyphMetrics *metrics, GLuint *texture, float coords[]);
public:
int m_code; //编码
GLuint m_texture; //纹理
float m_coords[4]; //坐标:left top right bottom
private:
glyphMetrics m_metrics;
};
class CPage //页面:纹理=1:1。一个纹理中管理着多个字符
{
public:
CPage();
~CPage();
/*!
* \brief: append 加当前字体加入这个纹理
*
* \param: wide 宽度
* \param: tall 高度
* \param: rgba 字体的内容
* \param: coords 字体在当前纹理中的坐标
* \returns: bool
* \author: wangqi
* \date: 2020/06/02 14:36
*
* TODO:
*
*/
bool append(int wide, int tall, byte *rgba, float coords[]);
GLuint getTexture(void); //得到纹理
private:
GLuint m_texture; //纹理
int m_wide; //宽度
int m_tall; //高度
int m_posx;
int m_posy;
int m_maxCharTall;
};
typedef std::map<int, CChar *> TCharMap;
//键值对:找到字符的位置
//key: 字符
//value: 字符的信息
FT_Library m_library;
FT_Face m_face;
bool m_antialias;
bool m_bold;
int m_tall;
int m_rgbaSize;
GLubyte *m_rgba;
TCharMap m_chars;
std::vector<CPage *> m_pages;
};
FT_Library m_library;
std::vector<CFont *> m_fonts; //一个字体库有多个字体
};
//------------------------------------------------------------
// CFont
//------------------------------------------------------------
CFontManager::CFont::CFont()
{
m_face = NULL;
m_rgba = NULL;
m_antialias = false;
m_bold = false;
m_tall = 0;
}
CFontManager::CFont::~CFont()
{
release();
}
bool CFontManager::CFont::create(FT_Library library, const char *filename, FT_Long face_index,
int tall, bool bold, bool italic, bool antialias)
{
FT_Error err;
if(tall > 256)
{
// Bigger than a page size
return false;
}
// 得到字符信息
if((err = FT_New_Face(library, filename, face_index, &m_face)) != FT_Err_Ok)
{
printf("FT_New_Face() Error %d\n", err);
return false;
}
// 设置像素
if((err = FT_Set_Pixel_Sizes(m_face, 0, tall)) != FT_Err_Ok)
{
printf("FT_Set_Pixel_Sizes() Error %d\n", err);
return false;
}
//m_rgbaSize = (tall * 2) * tall * 4; //源代码是*2,但是觉得博主写错了
m_rgbaSize = (tall) * tall * 4; //高*宽*4。但位图的高宽相同,故可以高*高*4;4代表RGBA四个通道
m_rgba = new GLubyte[m_rgbaSize];
if(m_rgba == NULL)
{
return false;
}
m_library = library;
m_antialias = antialias;
m_bold = bold;
m_tall = tall;
if(italic) //是否抗锯齿
{
FT_Matrix m;
m.xx = 0x10000L;
m.xy = 0.5f * 0x10000L;
m.yx = 0;
m.yy = 0x10000L;
FT_Set_Transform(m_face, &m, NULL);
}
return true;
}
void CFontManager::CFont::release(void)
{
FT_Error err;
if(m_face)
{
if((err = FT_Done_Face(m_face)) != FT_Err_Ok)
{
printf("FT_Done_Face() Error %d\n", err);
}
m_face = NULL;
}
if(m_rgba)
{
delete[] m_rgba;
m_rgba = NULL;
}
for(TCharMap::iterator it = m_chars.begin(); it != m_chars.end(); it++)
{
delete it->second;
it->second = NULL;
}
m_chars.clear();
for(size_t i = 0; i < m_pages.size(); i++)
{
delete m_pages[i];
m_pages[i] = NULL;
}
m_pages.clear();
}
bool CFontManager::CFont::getCharInfo(int code, glyphMetrics *metrics, GLuint *texture, float coords[])
{
// 记忆化搜索
TCharMap::iterator it = m_chars.find(code); //根据code获取CChar
if(it != m_chars.end()) //找到
{
// 得到绘制信息
it->second->getInfo(metrics, texture, coords); //字符、纹理、 坐标
return true;
}
// 没有找到
glyphMetrics gm;
if(loadChar(code, &gm) == false) //加载字符
{
return false;
}
CChar *ch = new CChar();
ch->m_code = code;
ch->setInfo(&gm);
// 追加到一张纸中
for(size_t i = 0; i < m_pages.size(); i++)
{
CPage *page = m_pages[i];
if(page->append(gm.width, gm.height, m_rgba, ch->m_coords))
{
// 这张纸还放的下
ch->m_texture = page->getTexture();
ch->getInfo(metrics, texture, coords);
m_chars.insert(TCharMap::value_type(code, ch));
return true;
}
}
// 之前的纸都放不下了,新建一张纸
CPage *page = new CPage();
if(page->append(gm.width, gm.height, m_rgba, ch->m_coords))
{
ch->m_texture = page->getTexture();
ch->getInfo(metrics, texture, coords);
m_chars.insert(TCharMap::value_type(code, ch));
m_pages.push_back(page);
return true;
}
delete ch;
delete page;
return false;
}
int CFontManager::CFont::getFontTall(void)
{
return m_tall;
}
// bitmap.width 位图宽度
// bitmap.rows 位图行数(高度)
// bitmap.pitch 位图一行占用的字节数
//MONO模式每1个像素仅用1bit保存,只有黑和白。
//1个byte可以保存8个像素,1个int可以保存8*4个像素。
void ConvertMONOToRGBA(FT_Bitmap *source, GLubyte *rgba)
{
GLubyte *s = source->buffer;
GLubyte *t = rgba;
for(GLuint y = source->rows; y > 0; y--)
{
GLubyte *ss = s;
GLubyte *tt = t;
for(GLuint x = source->width >> 3; x > 0; x--)
{
GLuint val = *ss;
for(GLuint i = 8; i > 0; i--)
{
tt[0] = tt[1] = tt[2] = tt[3] = (val & (1 << (i - 1))) ? 0xFF : 0x00;
tt += 4;
}
ss += 1;
}
GLuint rem = source->width & 7;
if(rem > 0)
{
GLuint val = *ss;
for(GLuint x = rem; x > 0; x--)
{
tt[0] = tt[1] = tt[2] = tt[3] = (val & 0x80) ? 0xFF : 0x00;
tt += 4;
val <<= 1;
}
}
s += source->pitch;
t += source->width * 4; //pitch
}
}
//GRAY模式1个像素用1个字节保存。
void ConvertGRAYToRGBA(FT_Bitmap *source, GLubyte *rgba)
{
for(GLuint y = 0; y < source->rows; y++)
{
for(GLuint x = 0; x < source->width; x++)
{
GLubyte *s = &source->buffer[(y * source->pitch) + x];
GLubyte *t = &rgba[((y * source->pitch) + x) * 4];
t[0] = t[1] = t[2] = 0xFF;
t[3] = *s;
}
}
}
bool CFontManager::CFont::loadChar(int code, glyphMetrics *metrics)
{
FT_Error err;
FT_UInt glyph_index = FT_Get_Char_Index(m_face, (FT_ULong)code); //得到下标
if(glyph_index > 0)
{
//将一个字形图像装到字形槽中
if((err = FT_Load_Glyph(m_face, glyph_index, FT_LOAD_DEFAULT)) == FT_Err_Ok)
{
FT_GlyphSlot glyph = m_face->glyph; //获得图形信息
// 模式:抗锯齿为256级灰度;FT_RENDER_MODE_MONO黑白
FT_Render_Mode render_mode = m_antialias ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO;
if(m_antialias && m_bold) //加粗
{
if((err = FT_Outline_EmboldenXY(&glyph->outline, 60, 60)) != FT_Err_Ok)
{
printf("FT_Outline_EmboldenXY() Error %d\n", err);
}
}
// 得到图形信息
if((err = FT_Render_Glyph(glyph, render_mode)) == FT_Err_Ok)
{
FT_Bitmap *bitmap = &glyph->bitmap; //得到位图
switch(bitmap->pixel_mode) //像素模式
{
case FT_PIXEL_MODE_MONO: //黑白
{
if(!m_antialias && m_bold)
{
// 加粗
if((err = FT_Bitmap_Embolden(m_library, bitmap, 60, 0)) != FT_Err_Ok)
{
printf("FT_Bitmap_Embolden() Error %d\n", err);
}
}
// 将黑白转成RGBA
ConvertMONOToRGBA(bitmap, m_rgba);
break;
}
case FT_PIXEL_MODE_GRAY: //灰度
{
ConvertGRAYToRGBA(bitmap, m_rgba); //灰度转RGBA
break;
}
default:
{
memset(m_rgba, 0xFF, m_rgbaSize);
break;
}
}
metrics->width = bitmap->width;
metrics->height = bitmap->rows;
metrics->horiBearingX = glyph->bitmap_left;
metrics->horiBearingY = glyph->bitmap_top;
metrics->horiAdvance = glyph->advance.x >> 6;
return true;
}
else
{
printf("FT_Render_Glyph() Error %d\n", err);
}
}
else
{
printf("FT_Load_Glyph() Error %d\n", err);
}
}
memset(metrics, 0, sizeof(glyphMetrics));
return false;
}
//------------------------------------------------------------
// CChar
//------------------------------------------------------------
void CFontManager::CFont::CChar::setInfo(glyphMetrics *metrics)
{
memcpy(&m_metrics, metrics, sizeof(glyphMetrics));
}
void CFontManager::CFont::CChar::getInfo(glyphMetrics *metrics, GLuint *texture, float coords[])
{
memcpy(metrics, &m_metrics, sizeof(glyphMetrics));
*texture = m_texture;
memcpy(coords, m_coords, sizeof(float) * 4);
}
//------------------------------------------------------------
// CPage 开辟一页的纹理,来存放多个字符
//------------------------------------------------------------
CFontManager::CFont::CPage::CPage() //一页对应一个纹理
{
m_wide = m_tall = 256; //高
m_posx = m_posy = 0;
// In a line, for a max height character
m_maxCharTall = 0;
glGenTextures(1, &m_texture); // Using your API here
glBindTexture(GL_TEXTURE_2D, m_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_wide, m_tall, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
CFontManager::CFont::CPage::~CPage()
{
// free the texture
}
bool CFontManager::CFont::CPage::append(int wide, int tall, byte *rgba, float coords[])
{
if(m_posy + tall > m_tall)
{
// not enough line space in this page
return false;
}
// If this line is full ...
if(m_posx + wide > m_wide)
{
int newLineY = m_posy + m_maxCharTall;
if(newLineY + tall > m_tall)
{
// No more space for new line in this page, should allocate a new one
return false;
}
// Begin in new line
m_posx = 0;
m_posy = newLineY;
// Reset
m_maxCharTall = 0;
}
glBindTexture(GL_TEXTURE_2D, m_texture);
glTexSubImage2D(GL_TEXTURE_2D, 0, m_posx, m_posy, wide, tall, GL_RGBA, GL_UNSIGNED_BYTE, rgba);
// 该字符的位置
coords[0] = m_posx / (float)m_wide; // left
coords[1] = m_posy / (float)m_tall; // top
coords[2] = (m_posx + wide) / (float)m_wide; // right
coords[3] = (m_posy + tall) / (float)m_tall; // bottom
m_posx += wide;
if(tall > m_maxCharTall)
{
m_maxCharTall = tall;
}
return true;
}
GLuint CFontManager::CFont::CPage::getTexture(void)
{
return m_texture;
}
//------------------------------------------------------------
// CFontManager
//------------------------------------------------------------
CFontManager::CFontManager()
{
m_library = NULL;
}
CFontManager::~CFontManager()
{
release();
}
bool CFontManager::initialize(void)
{
FT_Error err;
if((err = FT_Init_FreeType(&m_library)) != FT_Err_Ok) //初始化一个库
{
printf("FT_Init_FreeType() Error %d\n", err);
return false;
}
return true;
}
void CFontManager::release(void)
{
FT_Error err;
for(size_t i = 0; i < m_fonts.size(); i++)
{
delete m_fonts[i];
m_fonts[i] = NULL;
}
m_fonts.clear();
if((err = FT_Done_FreeType(m_library)) != FT_Err_Ok)
{
printf("FT_Done_FreeType() Error %d\n");
}
}
int CFontManager::createFont(const char *filename, int face, int tall, bool bold,
bool italic, bool antialias)
{
// 新建字体
CFont *font = new CFont();
if(font->create(m_library, filename, face, tall, bold, italic, antialias) != true)
{
delete font; //创建失败
return 0;
}
// 放入字体数组,进行管理
m_fonts.push_back(font);
return m_fonts.size();
}
#define CONVERT_FONT_INDEX(x) (((x) < 1 || (x) > (int)m_fonts.size()) ? -1 : (x) - 1)
bool CFontManager::getCharInfo(int font_index, int code, int *wide, int *tall, int *horiBearingX, int *horiBearingY, int *horiAdvance, GLuint *texture, float coords[])
{
int i = CONVERT_FONT_INDEX(font_index); //转换字符的下标
if(i == -1)
{
return false;
}
CFont *font = m_fonts[i]; //获得字体
glyphMetrics metrics; //绘制信息
if(font->getCharInfo(code, &metrics, texture, coords) == false) //获得绘制信息
{
return false;
}
// 解析绘制信息
*wide = metrics.width;
*tall = metrics.height;
*horiBearingX = metrics.horiBearingX;
*horiBearingY = metrics.horiBearingY;
*horiAdvance = metrics.horiAdvance;
return true;
}
int CFontManager::getFontTall(int font_index)
{
int i = CONVERT_FONT_INDEX(font_index);
if(i == -1)
{
return false;
}
CFont *font = m_fonts[i];
return font->getFontTall();
}
CFontManager g_FontManager;
int char_font;
void init(void)
{
// 清除颜色
glClearColor(0.0, 0.0, 0.0, 0.0);
// 混合模式
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// 初始化一个库
g_FontManager.initialize();
// 创建字体,返回字体集的个数
char_font = g_FontManager.createFont("C:\\WINDOWS\\Fonts\\simhei.ttf",
0,
32, //字体高度
false,
false,
true);
if(char_font == 0)//创建失败
{
printf("createFont failed\n");
}
}
wchar_t ciphertext[] =
{
L"你好吗?\n"
L"温馨提示:\n"
L"不要忘记明天上午 7:30-12:00 在公司的体检哦~\n"
L"体检前一天不要暴饮暴食,不要喝酒,不要进食太甜太咸的食物\n"
L"以免影响体检结果\n"
L"晚上10点后一般要求禁食。\n"
L"体检当天早晨千万不要吃早餐!\n"
L"不要吃早餐!\n"
L"不要吃早餐!\n"
L"绘制位置。忘记了,可费解为无法\n"
};
//#define DRAW_PAGE
/*!
* \brief: draw_string
*
* \param: x 绘制位置
* \param: y
* \param: font 字体
* \param: string 字符串
* \returns: void
* \author: wangqi
* \date: 2020/06/02 14:18
*
* TODO:
*
*/
void draw_string(int x, int y, int font_index, wchar_t *string)
{
if(!font_index) //字体
{
return;
}
int tall = g_FontManager.getFontTall(font_index); //得到字符高:初始化字体时设置的
// 绘制的初始位置
int dx = x;
int dy = y;
GLuint sglt = 0; //纹理编号,第几张纸
while(*string) // 遍历字符串
{
if(*string == L'\n') //回车
{
string++;
dx = x; //绘制的初始位置x
dy += tall + 2; //行间距
continue;
}
int cw, ct; //宽度、高度
int bx, by, av;
GLuint glt; //该字符所在的纸张
float crd[4]; //该字符的坐标
//得到字符信息
if(!g_FontManager.getCharInfo(font_index, *string, &cw, &ct, &bx, &by, &av, &glt, crd))
{
string++;
continue;
}
//大多数情况下多个字符都在同一个纹理中,避免频繁绑定纹理,可以提高效率
if(glt != sglt) //和原来的不一样,则绑定当前的纹理
{
glBindTexture(GL_TEXTURE_2D, glt); //绑定纹理
sglt = glt;
}
int px = dx + bx;
int py = dy - by;
// 开始绘制四边形
glBegin(GL_QUADS);
// 第一个点
glTexCoord2f(crd[0], crd[1]); //指定该字符在纹理中的坐标,阈值为[0, 1]
glVertex3f(px, py, 0.0f); //对应画布中的位置
// 第二个点
glTexCoord2f(crd[2], crd[1]);
glVertex3f(px + cw, py, 0.0f);
// 第三个点
glTexCoord2f(crd[2], crd[3]);
glVertex3f(px + cw, py + ct, 0.0f);
// 第四个点
glTexCoord2f(crd[0], crd[3]);
glVertex3f(px, py + ct, 0.0f);
// 结束绘制
glEnd();
dx += av;
string++;
}
}
void draw_page_texture(int x, int y, GLuint glt)
{
if(!glt)
{
glDisable(GL_TEXTURE_2D); //关闭
glColor4f(0.2, 0.2, 0.2, 1.0);
}
else
{
glEnable(GL_TEXTURE_2D); //开启
glBindTexture(GL_TEXTURE_2D, glt); //绑定纹理
glColor4f(1.0, 1.0, 1.0, 1.0);
}
int w = 256;
int t = 256;
glBegin(GL_QUADS);
glTexCoord2f(0.0, 0.0);
glVertex3f(x, y, 0.0f);
glTexCoord2f(1.0, 0.0);
glVertex3f(x + w, y, 0.0f);
glTexCoord2f(1.0, 1.0);
glVertex3f(x + w, y + t, 0.0f);
glTexCoord2f(0.0, 1.0);
glVertex3f(x, y + t, 0.0f);
glEnd();
}
void display(void)
{
// 清除缓存
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// 开启纹理
glEnable(GL_TEXTURE_2D);
// 设置画笔颜色,即字体颜色
glColor4f(0.0, 0.0, 1.0, 1.0);
// 绘制字符串
draw_string(10, 30, char_font, ciphertext);
// 绘制整个纹理
draw_page_texture(10, 350, 0); //绘制一个背景
draw_page_texture(10, 350, 1); //绘制第一张纸(即第一个纹理)
draw_page_texture(276, 350, 0); //绘制一个背景
draw_page_texture(276, 350, 2); //绘制第二张纸(即第二个纹理)
glutSwapBuffers();
glutPostRedisplay();
}
void reshape(int width, int height)
{
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, width, height, 0);
glMatrixMode(GL_MODELVIEW);
}
void keyboard(unsigned char key, int x, int y)
{
}
void main(int argc, char **argv)
{
glutInitWindowPosition(200, 200);
glutInitWindowSize(1200, 680);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL);
glutCreateWindow("FreeType OpenGL");
init();
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutKeyboardFunc(keyboard);
glutMainLoop();
}