OpenGL字体绘制

/*

 glfont.hpp

 sdragonx 2019-08-15 00:03:33

 opengl字体类,提供初学者参考学习

opengl初始化之后,创建字体
font.init(L"微软雅黑", 32, 512);

然后在绘制函数里面添加以下测试代码:

//开启2D模式,后面的800x600要根据窗口的实际客户区大小设置,不然缩放之后效果不好
push_view2D(0, 0, 800, 600);

wchar_t* str = L"abcdef字体绘制abcdef?123456ijk微软雅黑";

font.color = vec4ub(255, 255, 255, 128);
font.print(0, 0, str, wcslen(str));

font.tab_print(0, 32, L"abc\t123456\t7890", TEXT_MAX);

wchar_t* tabled = L"abcdef字体绘制\tabc制表符\t123456";
font.color = vec4ub(255, 0, 0, 255);
font.tab_print(0, 64, tabled, wcslen(tabled));

font.color = vec4ub(255, 0, 0, 255);
font.draw_text(0, 200, 200, 400, str, wcslen(str), PT_LEFT);
font.color = vec4ub(0, 255, 0, 255);
font.draw_text(300, 200, 200, 400, str, wcslen(str), PT_CENTER);
font.color = vec4ub(255, 0, 255, 255);
font.draw_text(600, 200, 200, 400, str, wcslen(str), PT_RIGHT);

pop_view();
//代码结束

*/
#ifndef GLFONT_HPP_20190815000333
#define GLFONT_HPP_20190815000333

#include 

#include 
#include 
#include 

//opengl头文件,根据环境更改
#ifdef __glew_h__
	#include 
#elif defined(__glad_h_)
	#include 
#else
	#include 
#endif

#if defined(__GNUC__) || defined(__clang__)
	#define TYPENAME typename
#else
	#define TYPENAME
#endif

#define CGL_DEFAULT_FONT_SIZE 16	//默认字体大小
#define TEXT_MAX UINT32_MAX 		//0xFFFFFFFF

namespace cgl{

#pragma pack(push, 1)

//大纹理里面小图块结构
class teximage
{
public:
	typedef teximage this_type;

public:
	intptr_t image;					//纹理
	uint16_t x, y, width, height;	//小图在大图里面的位置信息
	float u1, v1, u2, v2;			//小图的uv坐标信息

public:
	teximage():image(), x(), y(), width(), height(), u1(0.0f), v1(0.0f), u2(1.0f), v2(1.0f)
	{
	}

	this_type& operator=(const this_type& div)
	{
		image = div.image;
		x = div.x;
		y = div.y;
		width = div.width;
		height = div.height;
		u1 = div.u1;
		v1 = div.v1;
		u2 = div.u2;
		v2 = div.v2;
		return *this;
	}
};

//字符信息
//如果一个字符需要输出到left、top的位置,字符实际位置是left+x、top+y
//输出完毕之后,下一个字符的位置是left+next_x、top+next_y
struct char_info
{
	int16_t x;	//字符偏移位置
	int16_t y;
	int16_t next_x;//字符大小,下一字符偏移位置
	int16_t next_y;
};

//vec3
template
struct vec3
{
	T x, y, z;
};

typedef vec3 vec3i;
typedef vec3 vec3f;

//vec4
template
struct vec4
{
	union{
		T data[4];
		struct{
			T x, y, width, height;
		};
		struct{
            T red, green, blue, alpha;
        };
    };

	vec4() : x(), y(), width(), height(){/*void*/}
	vec4(T vx, T vy, T vw, T vh) : x(vx), y(vy), width(vw), height(vh){/*void*/}
};

typedef vec4 vec4i;
typedef vec4 vec4f;
typedef vec4 vec4ub;


template
struct vtx3t2c4
{
	typedef vtx3t2c4 this_type;
	typedef vec4 color_type;

	VT x, y, z;
	TT u, v;
	color_type color;

	vtx3t2c4() : x(), y(), z(), u(), v(), color(){/*void*/}
	vtx3t2c4(const vec3& vtx, TT tu, TT tv, const color_type& cc) :
		x(v.x), y(v.y), z(v.z), u(tu), v(tv), color(cc) { /*void*/ }

	vtx3t2c4(VT vx, VT vy, VT vz, TT vu, TT vv, const color_type& cc) :
		x(vx), y(vy), z(vz), u(vu), v(vv), color(cc) { /*void*/ }

	this_type& operator=(const vec3& p)
	{
		x = p.x;
		y = p.y;
		z = p.z;
		return *this;
	}
};

typedef vtx3t2c4 vtx3f;

#pragma pack(pop)

//---------------------------------------------------------------------------
//opengl的一些扩展函数

//2D视觉模式,如果你使用的是矩阵操作,把这里面的函数替换成矩阵操作
void push_view2D(int left, int top, int width, int height)
{
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
/*
#if CGL_COORDINATE == CGL_LOWER_LEFT
	//直角坐标系
	this->ortho(left, width, top, height, 0, INT16_MAX);
	//重新设置正面,默认GL_CCW
	glFrontFace(GL_CCW);

#else*/

	//windows坐标系
	glOrtho(left, left+width, top+height, top, 0, INT16_MAX);
	glFrontFace(GL_CW);
//#endif

	//十字坐标系
	//glOrtho(-width/2, width/2, -height/2, height/2, 0, INT_MAX);//

	//反转屏幕
	//glScalef(1.0f, -1.0f, 1.0f);
	//glTranslatef(0.0f, -height, 0.0f);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glTranslatef(0.375f, 0.375f, 0.0f);//GL_POINTS and GL_LINES don't touch the right pixels
	glDisable(GL_DEPTH_TEST);//关闭深度测试
	glDisable(GL_CULL_FACE); //关闭面剔除
}

//还原视觉模式
void pop_view()
{
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
}

//绘图函数,这个根据使用的库更改和优化
void vtx_begin(const vtx3f* vtx)
{
	glVertexPointer(3, GL_FLOAT, sizeof(vtx3f), vtx);
	glTexCoordPointer(2, GL_FLOAT, sizeof(vtx3f), &vtx->u);
	glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(vtx3f), vtx->color.data);

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glEnableClientState(GL_COLOR_ARRAY);
}

void vtx_end(const vtx3f* vtx)
{
	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glDisableClientState(GL_COLOR_ARRAY);
}

int draw_arrays(int shape, const vtx3f* vtx, size_t pos, size_t size)
{
	vtx_begin(vtx);
	glDrawArrays(shape, pos, size);
	vtx_end(vtx);
	return 0;
}

//绘制图片
int draw_image(intptr_t image, vec4ub color, float x, float y, float width, float height,
		float u1 = 0.0f, float v1 = 0.0f, float u2 = 1.0f, float v2 = 1.0f)
{
	vtx3f vtx[] = {
		vtx3f(x,         y,          0.0f, u1, v1, color),
		vtx3f(x + width, y,          0.0f, u2, v1, color),
		vtx3f(x + width, y + height, 0.0f, u2, v2, color),
		vtx3f(x        , y + height, 0.0f, u1, v2, color)
	};
	glBindTexture(GL_TEXTURE_2D, image);
	return draw_arrays(GL_TRIANGLE_FAN, vtx, 0, 4);
}

int draw_image(const teximage& image, vec4ub color, float x, float y, float width, float height)
{
	return draw_image(image.image, color, x, y, width, height, image.u1, image.v1, image.u2, image.v2);
}

//---------------------------------------------------------------------------
//GDI字体封装类
//有需要的,可以把这个类替换成freetype等其他字体库
//实现方法不变,直接替换掉这个类就好
//比如我用freetype2实现一个ftFont的类
//或者用stb_font(一个轻量级freetype库)实现一个stbFont类

class gdifont
{
private:
	HDC m_dc;			//内存DC
	HFONT m_font;		//字体句柄
	GLYPHMETRICS m_gm;	//字符模型信息
	MAT2 m_mat;			//转置矩阵,默认初始矩阵

	std::wstring m_ttfile;//用于保存单独字体文件的路径
	std::vector m_pixelbuf;//用于保存字符像素信息
	//std::vector m_fontResource;//内存字体

public:
	gdifont() : m_dc(NULL), m_font(NULL), m_ttfile(), m_pixelbuf()
	{
		//初始化字体转置矩阵,默认初始矩阵
		//这个矩阵可以实现字体的旋转、偏移、缩放等效果
		//2x2矩阵
		//1 0
		//0 1
		m_mat.eM11.value = 1;m_mat.eM11.fract = 0;
		m_mat.eM12.value = 0;m_mat.eM12.fract = 0;
		m_mat.eM21.value = 0;m_mat.eM21.fract = 0;
		m_mat.eM22.value = 1;m_mat.eM22.fract = 0;
	}

	//字体句柄
	HFONT handle()const
	{
		return m_font;
    }

	//创建字体
	int create(const wchar_t* fontname, int size, int charset = GB2312_CHARSET)
	{
		//如果需要,首先释放资源
        if(this->handle()){
			this->dispose();
		}

		//创建内存DC
		m_dc = CreateCompatibleDC(0);

		//创建字体
		m_font = CreateFontW(
            size, // logical height of font height
            0, // logical average character width
            0, // angle of escapement
            0, // base-line orientation angle
            0, // font weight
            0, // italic attribute flag
            0, // underline attribute flag
            0, // strikeout attribute flag
            charset, // character set identifier
            0, // output precision
            0, // clipping precision
            DEFAULT_QUALITY, // output quality
            DEFAULT_PITCH | FF_SWISS, // pitch and family
            fontname // pointer to typeface name string
		);

		//绑定字体到内存DC
		SelectObject(m_dc, m_font);

		return 0;
	}

	//加载单独的字体文件
	/*例如:
	font.load(
		"myfont.ttf",	//字体文件,windows系统支持的字体,目录可以是绝对路径,也可以是相对路径
		"字体名称",		//点开字体文件,上面显示的字体名称,比如“微软雅黑”
		16,				//字体大小
        GB2312_CHARSET);//如果是中文字体,要设置中文字符集
	*/

	int load(const wchar_t* filename, const wchar_t* fontname, int size, int charset = 0)
	{
		if(this->handle()){
			this->dispose();
		}

        m_ttfile = filename;
        AddFontResourceExW(m_ttfile.c_str(), FR_PRIVATE, 0);
		this->create(fontname, size, charset);

		return 0;
	}

	//加载内存、程序资源内的字体,这个暂时懒得实现了,有需要的可以查一下WINAPI实现
	/*
	void load_memory(...)
    {
        FILE* f = fopen(filename, "rb");
        fseek(f, 0, SEEK_END);
        m_fontResource.resize(ftell(f));
        fseek(f, 0, SEEK_SET);
        fread(&m_fontResource[0], 1, m_fontResource.size(), f);
        fclose(f);

        DWORD dwFonts = 0;
		m_fontH = (HFONT)AddFontMemResourceEx(&m_fontResource[0], m_fontResource.size(), 0, &dwFonts);
    }
    */

	//释放资源
	void dispose()
	{
		if(m_dc){
			DeleteDC(m_dc);
			m_dc = null;
		}

        if(m_font){
	    	DeleteObject(m_font);
    		m_font = null;
   		}

        if(!m_ttfile.empty()){
            RemoveFontResourceExW(m_ttfile.c_str(), FR_PRIVATE, 0);
            m_ttfile.clear();
        }

        //RemoveFontMemResourceEx(m_font);
	}

	//获取一个字体的位图和字符信息
	int render_char(wchar_t ch, char_info& info)
	{
		//获取字符位图空间大小
		int size = GetGlyphOutlineW(m_dc, ch, GGO_GRAY8_BITMAP, &m_gm, 0, NULL, &m_mat);
		//重新设置位图缓冲区大小
		m_pixelbuf.resize(size);
		//获得字符位图像素信息
		GetGlyphOutlineW(m_dc, ch, GGO_GRAY8_BITMAP, &m_gm, 64*64, &m_pixelbuf[0], &m_mat);
		//GetGlyphOutline获得的位图像素是64阶灰度,要转换成256阶灰度
		//当然如果你要通过shader渲染,并希望获得一些其他效果,可以不转换,或进行其他转换
		gray256();
        //填写一下字符信息
		info.x = m_gm.gmptGlyphOrigin.x;
		info.y = m_gm.gmptGlyphOrigin.y;
		info.next_x = m_gm.gmCellIncX;
		info.next_y = m_gm.gmBlackBoxY;
		return 0;
	}

	//位图宽度
	int width()const
	{
		return m_gm.gmBlackBoxX;
	}

	//位图高度
	int height()const
	{
		return m_gm.gmBlackBoxY;
	}

	//位图像素数据
	void* data()
	{
		return &m_pixelbuf[0];
    }

	//64阶灰度转256阶灰度
	void gray256()
	{
		BYTE* p = &m_pixelbuf[0];
		int c;
		//数据行是四字节对齐的
		DWORD linewidth = (m_gm.gmBlackBoxX + 3) & 0xFFFFFFFC;
		for(size_t y=0; y 255)c = 255;//约束在0~255范围之内
				p[x] = c;
			}
			p += linewidth;//移动到下一行
		}
	}

	//测试获取的位图,画到一个HDC上面
	#ifdef _DEBUG
	void paint(HDC dc)
	{
		BYTE* p = &m_pixelbuf[0];
		int c;
		//数据行是四字节对齐的
		DWORD linewidth = (m_gm.gmBlackBoxX + 3) & 0xFFFFFFFC;
		for(size_t y=0; y
class imagelist
{
public:
	struct ITEM
	{
		teximage image;
		U data;
	};

	typedef const ITEM* item_type;
	typedef typename std::map map_type;
	typedef typename map_type::iterator iterator;
	typedef typename map_type::const_iterator const_iterator;

private:
	std::vector m_texlist;	//保存的纹理页
	map_type m_itemlist;				//小图信息列表,使用std::map组织,也可以根据需要用数组组织
	GLenum m_format;//纹理格式
	int m_width;	//纹理大小
	int m_height;
	int m_filter;	//纹理过滤方式
	int m_size;		//小图块大小,只记录高度
	int m_u, m_v;	//当前小图块添加位置

public:
	imagelist():m_texlist(), m_itemlist(), m_format(GL_RGBA), m_filter(GL_LINEAR),
		m_width(512), m_height(512), m_size(16), m_u(0), m_v(0)
	{
	}

	~imagelist()
	{
		this->dispose();
	}

	//初始化创建图集
	int create(size_t width, size_t height, size_t size, GLenum format = GL_RGBA, GLenum filter = GL_LINEAR)
	{
		this->dispose();
		m_width  = width;
		m_height = height;
		m_format = format,
		m_size   = size;
		m_filter = filter;
		m_u = m_v = 0;
		return 0;
	}

	//释放资源
	void dispose()
	{
		m_itemlist.clear();
		if(!m_texlist.empty()){
			//删除所有纹理页
			glDeleteTextures(m_texlist.size(), &m_texlist[0]);
			m_texlist.clear();
		}
		m_u = m_v = 0;
	}

	//当前缓存的小图块数量
	size_t size()const
	{
		return m_imglist.size();
	}

	//添加一个小图块
	int insert(const T& index, int width, int height, GLenum format, void* data, const U& userdata)
	{
		//首先移动坐标位置
		position_move(width);
		//绑定当前纹理,也就是图集的最后一个
		glBindTexture(GL_TEXTURE_2D, m_texlist.back());
		//更新纹理局部像素
		glTexSubImage2D(GL_TEXTURE_2D, 0, m_u, m_v, width, std::min(m_size, height), format, GL_UNSIGNED_BYTE, data);
		//保存信息
		ITEM item;
		item.image.image = m_texlist.back();
		item.image.x = m_u;
		item.image.y = m_v;
		item.image.width = width;
		item.image.height = std::min(height, m_size);
		item.image.u1 = float(m_u) / m_width;
		item.image.v1 = float(m_v) / m_height;
		item.image.u2 = float(m_u+width)/m_width;
		item.image.v2 = float(m_v+std::min(m_size, height))/m_height;
		item.data = userdata;
		m_itemlist[index] = item;
		//x方向移动坐标
		m_u += width + 1;//做一个像素的间距
		return index;
	}

	//查询图块信息
	item_type items(const T& index)const
	{
		const_iterator itr = m_itemlist.find(index);
		if(itr != m_itemlist.end()){
			return &itr->second;
		}
		else{
			return null;
		}
	}

	//查询图块是否存在
	bool exist(const T& index)const
	{
		return items(index);
	}

private:
	void position_move(int width)
	{
		GLuint tex = 0;
		width += 1;//做一个像素的间距
		if(m_u + width > m_width){//换行
			m_u = 0;
			m_v += m_size + 1;
		}

		//创建新纹理页
		if(m_texlist.empty() || m_v + m_size > m_height){
			glGenTextures(1, &tex);
			glBindTexture(GL_TEXTURE_2D, tex);
			glTexImage2D(GL_TEXTURE_2D, 0, m_format, m_width, m_height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, 0);

			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_filter);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_filter);

			m_texlist.push_back(tex);
			m_u = 0;
			m_v = 0;
		}
	}

	teximage* BindTexture(int index)
	{
		iterator itr = m_imglist.find(index);
		if(itr!=m_imglist.end())
		{
			glBindTexture(GL_TEXTURE_2D, itr->second.image.image);
			return &itr->second.image;
		}
		return NULL;
	}
};

//---------------------------------------------------------------------------
//glfont 字体类
//
//gles对GL_ALPHA8支持貌似不好,可以替换成GL_RGBA(需要将256灰度转换成RGBA格式)
//或者使用GL_RED等单通道格式,通过shader渲染字体
//

//draw_text字符串绘制参数
#define PT_LEFT      0x00000000
#define PT_RIGHT     0x00000001
#define PT_CENTER    0x00000002
#define PT_SHADOW    0x00010000
#define PT_CALCRECT  0x80000000

class glfont
{
public:
	typedef imagelist imagelist_type;

	typedef TYPENAME imagelist_type::item_type char_item;

	//const static int SHADOW_SIZE = 2;	//阴影大小,带阴影的字体,这个实现代码太长
	enum{
		TEXTURE_SIZE = 1024,	//默认纹理大小

		TEX_FORMAT = GL_ALPHA8,	//PC默认使用GL_ALPHA8纹理格式
		SRC_FORMAT = GL_ALPHA,	//位图数据默认格式

		TAB_WIDTH = 4			//制表符宽度
	};

	struct PT_WORD
	{
		const wchar_t* begin;
		const wchar_t* end;
		size_t width;
	};

private:
	gdifont m_font;		//字体类,可以替换成其他字体类
	std::wstring m_name;//字体名字
	int m_size;			//字体大小
	imagelist_type m_imagelist;//图集类

public:
	vec4ub color;	//字体颜色

public:
	glfont() : m_font(), m_name(), m_size(), m_imagelist(), color(255, 255, 255, 255)
	{

	}

	void init(const wchar_t* fontname, int size = CGL_DEFAULT_FONT_SIZE, int texture_size = TEXTURE_SIZE)
	{
		m_name = fontname;
		m_size = size;
		//m_imagelist = imagelist;
		m_font.create(fontname, size);
		m_imagelist.create(texture_size, texture_size, size, TEX_FORMAT, GL_NEAREST);
	}

	void clear()
	{
		m_imagelist.dispose();
	}

	void dispose()
	{
		m_imagelist.dispose();
		m_size = 0;
	}

	//获得字符item
	char_item char_items(wchar_t ch)
	{
		if(!m_imagelist.exist(ch)){
			make_char(ch);
		}
		return m_imagelist.items(ch);
	}

	//获得字符宽度
	int char_width(wchar_t ch)
	{
        char_item item;
		if(!m_imagelist.exist(ch)){
			make_char(ch);
		}
		item = m_imagelist.items(ch);
		return item ? item->data.next_x : 0;
    }

	//获取字符高度
	int char_height() { return m_size; }

	//获取字符串宽度
	int text_width(const wchar_t* text, size_t length)
	{
        int width = 0;
		for(size_t i=0; iimage,
			color,
			x + item->data.x,
			y + m_size - item->data.y,
			item->image.width,
			item->image.height);

		return item->data.next_x;
	}

	//绘制一行字体,不支持制表符
	int print(int x, int y, const wchar_t* text, size_t length)
	{
		if(length == TEXT_MAX)length = wcslen(text);
		for(size_t i=0; ichar_height() >> 1);
		if(length == TEXT_MAX)length = wcslen(text);
		for(size_t i=0; ichar_height() >> 1);
	int n;// = 0;
	int l_width = 0;

	for(; l_end < end; ++l_end){
		if(*l_end == '\r'){
			continue;
		}
		else if(*l_end == '\n'){//next line
			break;
		}
		//else if(*l_end == ' ')//add word
		else if(*l_end == '\t'){
			n = align_next(l_width, tab);
			if(n < width){
				l_width = n;
			}
			else{
				//break;
				return l_width;
			}
		}
		else{
			n = this->char_width(*l_end);
			if(l_width + n < width){
				l_width += n;
			}
			else{//next line
				//break;
				return l_width;
			}
		}
	}
	return l_width;
}

int glfont::draw_text(int left, int top, int width, int height, const wchar_t* text, size_t length, int style)
{
	int px = 0, py = top;	//字符绘制位置
	//int chwidth = 0;		//字符宽度
	int ch_size = this->char_height();
	if(length == TEXT_MAX)length = wcslen(text);
	int tab = TAB_WIDTH * (ch_size >> 1);

	//vec4ub c = dc->color;

	const wchar_t* end = text + length;
	const wchar_t* l_begin;
	const wchar_t* l_end = text;
	int l_width = 0;
	std::vector words;

	int x = 0;

	while(l_end < end)
	{
		//l_width = 0;
		l_begin = l_end;
		//word_begin = l_end;
		//get line
		l_width = get_tabled_line(l_end, end, width);


		px = left;
		if(style & PT_RIGHT){
			px += width - l_width;
		}
		else if(style & PT_CENTER){
			px += (width - l_width) / 2;
		}
		if(l_begin != l_end && !(style & PT_CALCRECT)){
			if(style & PT_SHADOW){
				//dc->color = shadow_color;
				//draw_shadow(dc, px, py, text+begin, end-begin);
			}
			//dc->color = c;

			x = 0;
			for(; l_begin < l_end; ++l_begin)
			{
				if(*l_begin == '\t'){
					x = align_next(x, tab);
					continue;
				}
				else if(*l_begin == '\r' || *l_begin == '\n'){
					continue;
				}
				x += put_char(px + x, py, *l_begin);
			}
		}

		if(*l_end == '\n'){//next line
			++l_end;
		}

		py += ch_size + (ch_size / 8);// 1/8 line space
		if(int(top + height) < py + ch_size){
			break;
		}
	}

	return py - top;
}


}//end namespace cgl

#endif //GLFONT_HPP_20190815000333

  

转载于:https://www.cnblogs.com/sdragonx/p/11357778.html

你可能感兴趣的:(OpenGL字体绘制)