【OpenGL】使用FreeType库加载字体并在GL中绘制文字
FreeType用起来比较麻烦,这里写了一份简单的示例代码,仅供参考。
实现了FT库生成字符位图,并上传到GL纹理。
实现了字符位图缓存功能,多个字符图像保存在同一个纹理中。
实现了简单的字体管理框架。
实现了简单的加粗和倾斜效果。
实现了反锯齿开关,并且兼容加粗倾斜效果。
代码如下:
1 // OpenGL library 2 #include <gl/glut.h> 3 4 // Std misc 5 #include <map> 6 #include <vector> 7 8 // FreeType library 9 #include <ft2build.h> 10 #include FT_FREETYPE_H 11 #include FT_BITMAP_H 12 #include FT_OUTLINE_H 13 14 15 #ifdef CreateFont 16 #undef CreateFont 17 #endif 18 19 typedef unsigned char byte; 20 21 class CFontManager 22 { 23 public: 24 CFontManager(); 25 ~CFontManager(); 26 27 bool initialize(void); 28 void release(void); 29 int createFont(const char *filename, int face, int tall, bool bold, bool italic, bool antialias); 30 bool getCharInfo(int font_index, int code, int *wide, int *tall, int *horiBearingX, int *horiBearingY, int *horiAdvance, GLuint *texture, float coords[]); 31 int getFontTall(int font_index); 32 33 private: 34 struct glyphMetrics 35 { 36 int width; 37 int height; 38 int horiBearingX; 39 int horiBearingY; 40 int horiAdvance; 41 //int vertBearingX; 42 //int vertBearingY; 43 //int vertAdvance; 44 }; 45 46 class CFont 47 { 48 public: 49 CFont(); 50 ~CFont(); 51 52 bool create(FT_Library library, const char *filename, FT_Long face_index, int tall, bool bold, bool italic, bool antialias); 53 void release(void); 54 bool getCharInfo(int code, glyphMetrics *metrics, GLuint *texture, float coords[]); 55 int getFontTall(void); 56 57 private: 58 bool loadChar(int code, glyphMetrics *metrics); 59 60 class CChar 61 { 62 public: 63 void setInfo(glyphMetrics *metrics); 64 void getInfo(glyphMetrics *metrics, GLuint *texture, float coords[]); 65 66 public: 67 int m_code; 68 GLuint m_texture; 69 float m_coords[4]; // left top right bottom 70 71 private: 72 glyphMetrics m_metrics; 73 }; 74 75 class CPage 76 { 77 public: 78 CPage(); 79 ~CPage(); 80 81 bool append(int wide, int tall, byte *rgba, float coords[]); 82 GLuint getTexture(void); 83 84 private: 85 GLuint m_texture; 86 int m_wide; 87 int m_tall; 88 int m_posx; 89 int m_posy; 90 int m_maxCharTall; 91 }; 92 93 typedef std::map<int, CChar *> TCharMap; 94 95 FT_Library m_library; 96 FT_Face m_face; 97 bool m_antialias; 98 bool m_bold; 99 int m_tall; 100 int m_rgbaSize; 101 GLubyte *m_rgba; 102 TCharMap m_chars; 103 std::vector<CPage *> m_pages; 104 }; 105 106 FT_Library m_library; 107 std::vector<CFont *> m_fonts; 108 }; 109 110 111 112 //------------------------------------------------------------ 113 // CFont 114 //------------------------------------------------------------ 115 CFontManager::CFont::CFont() 116 { 117 m_face = NULL; 118 m_rgba = NULL; 119 m_antialias = false; 120 m_bold = false; 121 m_tall = 0; 122 } 123 124 CFontManager::CFont::~CFont() 125 { 126 release(); 127 } 128 129 bool CFontManager::CFont::create(FT_Library library, const char *filename, FT_Long face_index, int tall, bool bold, bool italic, bool antialias) 130 { 131 FT_Error err; 132 133 if (tall > 256) 134 { 135 // Bigger than a page size 136 return false; 137 } 138 139 if ((err = FT_New_Face(library, filename, face_index, &m_face)) != FT_Err_Ok) 140 { 141 printf("FT_New_Face() Error %d\n", err); 142 return false; 143 } 144 145 if ((err = FT_Set_Pixel_Sizes(m_face, 0, tall)) != FT_Err_Ok) 146 { 147 printf("FT_Set_Pixel_Sizes() Error %d\n", err); 148 return false; 149 } 150 151 m_rgbaSize = (tall * 2) * tall * 4; 152 153 m_rgba = new GLubyte[m_rgbaSize]; 154 155 if (m_rgba == NULL) 156 { 157 return false; 158 } 159 160 m_library = library; 161 m_antialias = antialias; 162 m_bold = bold; 163 m_tall = tall; 164 165 if (italic) 166 { 167 FT_Matrix m; 168 m.xx = 0x10000L; 169 m.xy = 0.5f * 0x10000L; 170 m.yx = 0; 171 m.yy = 0x10000L; 172 FT_Set_Transform(m_face, &m, NULL); 173 } 174 175 return true; 176 } 177 178 void CFontManager::CFont::release(void) 179 { 180 FT_Error err; 181 182 if (m_face) 183 { 184 if ((err = FT_Done_Face(m_face)) != FT_Err_Ok) 185 { 186 printf("FT_Done_Face() Error %d\n", err); 187 } 188 m_face = NULL; 189 } 190 191 if (m_rgba) 192 { 193 delete[] m_rgba; 194 m_rgba = NULL; 195 } 196 197 for (TCharMap::iterator it = m_chars.begin(); it != m_chars.end(); it++) 198 { 199 delete it->second; 200 it->second = NULL; 201 } 202 203 m_chars.clear(); 204 205 for (size_t i = 0; i < m_pages.size(); i++) 206 { 207 delete m_pages[i]; 208 m_pages[i] = NULL; 209 } 210 211 m_pages.clear(); 212 } 213 214 bool CFontManager::CFont::getCharInfo(int code, glyphMetrics *metrics, GLuint *texture, float coords[]) 215 { 216 // fast find it 217 TCharMap::iterator it = m_chars.find(code); 218 219 if (it != m_chars.end()) 220 { 221 it->second->getInfo(metrics, texture, coords); 222 return true; 223 } 224 225 glyphMetrics gm; 226 227 if (loadChar(code, &gm) == false) 228 { 229 return false; 230 } 231 232 CChar *ch = new CChar(); 233 234 ch->m_code = code; 235 ch->setInfo(&gm); 236 237 for (size_t i = 0; i < m_pages.size(); i++) 238 { 239 CPage *page = m_pages[i]; 240 241 if (page->append(gm.width, gm.height, m_rgba, ch->m_coords)) 242 { 243 ch->m_texture = page->getTexture(); 244 ch->getInfo(metrics, texture, coords); 245 m_chars.insert(TCharMap::value_type(code, ch)); 246 return true; 247 } 248 } 249 250 CPage *page = new CPage(); 251 252 if (page->append(gm.width, gm.height, m_rgba, ch->m_coords)) 253 { 254 ch->m_texture = page->getTexture(); 255 ch->getInfo(metrics, texture, coords); 256 m_chars.insert(TCharMap::value_type(code, ch)); 257 m_pages.push_back(page); 258 return true; 259 } 260 261 delete ch; 262 delete page; 263 264 return false; 265 } 266 267 int CFontManager::CFont::getFontTall(void) 268 { 269 return m_tall; 270 } 271 272 // bitmap.width 位图宽度 273 // bitmap.rows 位图行数(高度) 274 // bitmap.pitch 位图一行占用的字节数 275 276 //MONO模式每1个像素仅用1bit保存,只有黑和白。 277 //1个byte可以保存8个像素,1个int可以保存8*4个像素。 278 void ConvertMONOToRGBA(FT_Bitmap *source, GLubyte *rgba) 279 { 280 GLubyte *s = source->buffer; 281 GLubyte *t = rgba; 282 283 for (GLuint y = source->rows; y > 0; y--) 284 { 285 GLubyte *ss = s; 286 GLubyte *tt = t; 287 288 for (GLuint x = source->width >> 3; x > 0; x--) 289 { 290 GLuint val = *ss; 291 292 for (GLuint i = 8; i > 0; i--) 293 { 294 tt[0] = tt[1] = tt[2] = tt[3] = ( val & (1<<(i-1)) ) ? 0xFF : 0x00; 295 tt += 4; 296 } 297 298 ss += 1; 299 } 300 301 GLuint rem = source->width & 7; 302 303 if (rem > 0) 304 { 305 GLuint val = *ss; 306 307 for (GLuint x = rem; x > 0; x--) 308 { 309 tt[0] = tt[1] = tt[2] = tt[3] = ( val & 0x80 ) ? 0xFF : 0x00; 310 tt += 4; 311 val <<= 1; 312 } 313 } 314 315 s += source->pitch; 316 t += source->width * 4; //pitch 317 } 318 } 319 320 //GRAY模式1个像素用1个字节保存。 321 void ConvertGRAYToRGBA(FT_Bitmap *source, GLubyte *rgba) 322 { 323 for (GLuint y = 0; y < source->rows; y++) 324 { 325 for (GLuint x = 0; x < source->width; x++) 326 { 327 GLubyte *s = &source->buffer[(y * source->pitch) + x]; 328 GLubyte *t = &rgba[((y * source->pitch) + x) * 4]; 329 330 t[0] = t[1] = t[2] = 0xFF; 331 t[3] = *s; 332 } 333 } 334 } 335 336 bool CFontManager::CFont::loadChar(int code, glyphMetrics *metrics) 337 { 338 FT_Error err; 339 340 FT_UInt glyph_index = FT_Get_Char_Index(m_face, (FT_ULong)code); 341 342 if (glyph_index > 0) 343 { 344 if ((err = FT_Load_Glyph(m_face, glyph_index, FT_LOAD_DEFAULT)) == FT_Err_Ok) 345 { 346 FT_GlyphSlot glyph = m_face->glyph; 347 348 FT_Render_Mode render_mode = m_antialias ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO; 349 350 if (m_antialias && m_bold) 351 { 352 if ((err = FT_Outline_EmboldenXY(&glyph->outline, 60, 60)) != FT_Err_Ok) 353 { 354 printf("FT_Outline_EmboldenXY() Error %d\n", err); 355 } 356 } 357 358 if ((err = FT_Render_Glyph(glyph, render_mode)) == FT_Err_Ok) 359 { 360 FT_Bitmap *bitmap = &glyph->bitmap; 361 362 switch (bitmap->pixel_mode) 363 { 364 case FT_PIXEL_MODE_MONO: 365 { 366 if (!m_antialias && m_bold) 367 { 368 if ((err = FT_Bitmap_Embolden(m_library, bitmap, 60, 0)) != FT_Err_Ok) 369 { 370 printf("FT_Bitmap_Embolden() Error %d\n", err); 371 } 372 } 373 ConvertMONOToRGBA(bitmap, m_rgba); 374 break; 375 } 376 case FT_PIXEL_MODE_GRAY: 377 { 378 ConvertGRAYToRGBA(bitmap, m_rgba); 379 break; 380 } 381 default: 382 { 383 memset(m_rgba, 0xFF, m_rgbaSize); 384 break; 385 } 386 } 387 388 metrics->width = bitmap->width; 389 metrics->height = bitmap->rows; 390 metrics->horiBearingX = glyph->bitmap_left; 391 metrics->horiBearingY = glyph->bitmap_top; 392 metrics->horiAdvance = glyph->advance.x >> 6; 393 394 return true; 395 } 396 else 397 { 398 printf("FT_Render_Glyph() Error %d\n", err); 399 } 400 } 401 else 402 { 403 printf("FT_Load_Glyph() Error %d\n", err); 404 } 405 } 406 407 memset(metrics, 0, sizeof(glyphMetrics)); 408 409 return false; 410 } 411 412 //------------------------------------------------------------ 413 // CChar 414 //------------------------------------------------------------ 415 void CFontManager::CFont::CChar::setInfo(glyphMetrics *metrics) 416 { 417 memcpy(&m_metrics, metrics, sizeof(glyphMetrics)); 418 } 419 420 void CFontManager::CFont::CChar::getInfo(glyphMetrics *metrics, GLuint *texture, float coords[]) 421 { 422 memcpy(metrics, &m_metrics, sizeof(glyphMetrics)); 423 424 *texture = m_texture; 425 memcpy(coords, m_coords, sizeof(float)*4); 426 } 427 428 //------------------------------------------------------------ 429 // CPage 430 //------------------------------------------------------------ 431 CFontManager::CFont::CPage::CPage() 432 { 433 m_wide = m_tall = 256; 434 m_posx = m_posy = 0; 435 436 // In a line, for a max height character 437 m_maxCharTall = 0; 438 439 glGenTextures(1, &m_texture); // Using your API here 440 glBindTexture(GL_TEXTURE_2D, m_texture); 441 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_wide, m_tall, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); 442 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 443 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 444 } 445 446 CFontManager::CFont::CPage::~CPage() 447 { 448 // free the texture 449 } 450 451 bool CFontManager::CFont::CPage::append(int wide, int tall, byte *rgba, float coords[]) 452 { 453 if (m_posy + tall > m_tall) 454 { 455 // not enough line space in this page 456 return false; 457 } 458 459 // If this line is full ... 460 if (m_posx + wide > m_wide) 461 { 462 int newLineY = m_posy + m_maxCharTall; 463 464 if (newLineY + tall > m_tall) 465 { 466 // No more space for new line in this page, should allocate a new one 467 return false; 468 } 469 470 // Begin in new line 471 m_posx = 0; 472 m_posy = newLineY; 473 // Reset 474 m_maxCharTall = 0; 475 } 476 477 glBindTexture(GL_TEXTURE_2D, m_texture); 478 glTexSubImage2D(GL_TEXTURE_2D, 0, m_posx, m_posy, wide, tall, GL_RGBA, GL_UNSIGNED_BYTE, rgba); 479 480 coords[0] = m_posx / (float)m_wide; // left 481 coords[1] = m_posy / (float)m_tall; // top 482 coords[2] = (m_posx + wide) / (float)m_wide; // right 483 coords[3] = (m_posy + tall) / (float)m_tall; // bottom 484 485 m_posx += wide; 486 487 if (tall > m_maxCharTall) 488 { 489 m_maxCharTall = tall; 490 } 491 492 return true; 493 } 494 495 GLuint CFontManager::CFont::CPage::getTexture(void) 496 { 497 return m_texture; 498 } 499 500 //------------------------------------------------------------ 501 // CFontManager 502 //------------------------------------------------------------ 503 CFontManager::CFontManager() 504 { 505 m_library = NULL; 506 } 507 508 CFontManager::~CFontManager() 509 { 510 release(); 511 } 512 513 bool CFontManager::initialize(void) 514 { 515 FT_Error err; 516 517 if ((err = FT_Init_FreeType(&m_library)) != FT_Err_Ok) 518 { 519 printf("FT_Init_FreeType() Error %d\n", err); 520 return false; 521 } 522 523 return true; 524 } 525 526 void CFontManager::release(void) 527 { 528 FT_Error err; 529 530 for (size_t i = 0; i < m_fonts.size(); i++) 531 { 532 delete m_fonts[i]; 533 m_fonts[i] = NULL; 534 } 535 536 m_fonts.clear(); 537 538 if ((err = FT_Done_FreeType(m_library)) != FT_Err_Ok) 539 { 540 printf("FT_Done_FreeType() Error %d\n"); 541 } 542 } 543 544 int CFontManager::createFont(const char *filename, int face, int tall, bool bold, bool italic, bool antialias) 545 { 546 CFont *font = new CFont(); 547 548 if (font->create(m_library, filename, face, tall, bold, italic, antialias) != true) 549 { 550 delete font; 551 return 0; 552 } 553 554 m_fonts.push_back(font); 555 556 return m_fonts.size(); 557 } 558 559 #define CONVERT_FONT_INDEX(x) (((x) < 1 || (x) > (int)m_fonts.size()) ? -1 : (x) - 1) 560 561 bool CFontManager::getCharInfo(int font_index, int code, int *wide, int *tall, int *horiBearingX, int *horiBearingY, int *horiAdvance, GLuint *texture, float coords[]) 562 { 563 int i = CONVERT_FONT_INDEX(font_index); 564 565 if (i == -1) 566 { 567 return false; 568 } 569 570 CFont *font = m_fonts[i]; 571 572 glyphMetrics metrics; 573 574 if (font->getCharInfo(code, &metrics, texture, coords) == false) 575 { 576 return false; 577 } 578 579 *wide = metrics.width; 580 *tall = metrics.height; 581 *horiBearingX = metrics.horiBearingX; 582 *horiBearingY = metrics.horiBearingY; 583 *horiAdvance = metrics.horiAdvance; 584 585 return true; 586 } 587 588 int CFontManager::getFontTall(int font_index) 589 { 590 int i = CONVERT_FONT_INDEX(font_index); 591 592 if (i == -1) 593 { 594 return false; 595 } 596 597 CFont *font = m_fonts[i]; 598 599 return font->getFontTall(); 600 } 601 602 CFontManager g_FontManager; 603 604 605 int char_font; 606 607 608 void init(void) 609 { 610 glClearColor(0.0, 0.0, 0.0, 0.0); 611 612 glEnable(GL_BLEND); 613 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 614 615 g_FontManager.initialize(); 616 617 char_font = g_FontManager.createFont("FZDH_GBK.TTF", 0, 32, false, false, true); 618 619 if (char_font == 0) 620 { 621 printf("createFont failed\n"); 622 } 623 } 624 625 wchar_t ciphertext[] = 626 { 627 L"第一我做的是人气,不是他妈的信仰不是他妈的信任\n" 628 L"我想信任来着,一群狼心的人就知道玩游戏。我投入的你们给我一分钱回报了么。\n" 629 L"不懂少在那里嫌弃我,白花花的银子砸出来的东西免费给你们玩你们还这bb那bb\n" 630 L"不管有没有人骂我我只在乎有没有人看我 懂么?\n" 631 L"第二我你们尽管骂我\n" 632 L"第三我的事情是我的自由\n" 633 L"第四我一直很讨厌csol\n" 634 L"第五世界上最失败的一次行动就是创建百度贴吧\n" 635 L"第六 一群二逼骂个毛线管我屁事\n" 636 L"http://tieba.baidu.com/p/3144980358" 637 }; 638 639 //#define DRAW_PAGE 640 641 void draw_string(int x, int y, int font, wchar_t *string) 642 { 643 if (!font) 644 return; 645 646 int tall = g_FontManager.getFontTall(font); 647 648 int dx = x; 649 int dy = y; 650 651 GLuint sglt = 0; 652 653 while (*string) 654 { 655 if (*string == L'\n') 656 { 657 string++; 658 dx = x; 659 dy += tall + 2; //row spacing 660 continue; 661 } 662 663 int cw, ct, bx, by, av; 664 GLuint glt; 665 float crd[4]; 666 667 if (!g_FontManager.getCharInfo(font, *string, &cw, &ct, &bx, &by, &av, &glt, crd)) 668 { 669 string++; 670 continue; 671 } 672 673 //大多数情况下多个字符都在同一个纹理中,避免频繁绑定纹理,可以提高效率 674 if (glt != sglt) 675 { 676 glBindTexture(GL_TEXTURE_2D, glt); 677 sglt = glt; 678 } 679 680 int px = dx + bx; 681 int py = dy - by; 682 683 glBegin(GL_QUADS); 684 glTexCoord2f(crd[0], crd[1]); 685 glVertex3f(px, py, 0.0f); 686 glTexCoord2f(crd[2], crd[1]); 687 glVertex3f(px + cw, py, 0.0f); 688 glTexCoord2f(crd[2], crd[3]); 689 glVertex3f(px + cw, py + ct, 0.0f); 690 glTexCoord2f(crd[0], crd[3]); 691 glVertex3f(px, py + ct, 0.0f); 692 glEnd(); 693 694 dx += av; 695 696 string++; 697 } 698 } 699 700 void draw_page_texture(int x, int y, GLuint glt) 701 { 702 if (!glt) 703 { 704 glDisable(GL_TEXTURE_2D); 705 glColor4f(0.2, 0.2, 0.2, 1.0); 706 } 707 else 708 { 709 glEnable(GL_TEXTURE_2D); 710 glBindTexture(GL_TEXTURE_2D, glt); 711 glColor4f(1.0, 1.0, 1.0, 1.0); 712 } 713 714 int w = 256; 715 int t = 256; 716 717 glBegin(GL_QUADS); 718 glTexCoord2f(0.0, 0.0); 719 glVertex3f(x, y, 0.0f); 720 glTexCoord2f(1.0, 0.0); 721 glVertex3f(x + w, y, 0.0f); 722 glTexCoord2f(1.0, 1.0); 723 glVertex3f(x + w, y + t, 0.0f); 724 glTexCoord2f(0.0, 1.0); 725 glVertex3f(x, y + t, 0.0f); 726 glEnd(); 727 } 728 729 void display(void) 730 { 731 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 732 733 glEnable(GL_TEXTURE_2D); 734 glColor4f(1.0, 1.0, 1.0, 1.0); 735 draw_string(10, 30, char_font, ciphertext); 736 737 draw_page_texture(10, 350, 0); //background 738 draw_page_texture(10, 350, 1); //page1 texture 739 740 draw_page_texture(276, 350, 0); //background 741 draw_page_texture(276, 350, 2); //page2 texture 742 743 glutSwapBuffers(); 744 glutPostRedisplay(); 745 } 746 747 void reshape(int width, int height) 748 { 749 glViewport(0, 0, width, height); 750 751 glMatrixMode(GL_PROJECTION); 752 glLoadIdentity(); 753 gluOrtho2D(0, width, height, 0); 754 glMatrixMode(GL_MODELVIEW); 755 } 756 757 void keyboard(unsigned char key, int x, int y) 758 { 759 } 760 761 void main(int argc, char **argv) 762 { 763 glutInitWindowPosition(200, 200); 764 glutInitWindowSize(1200, 680); 765 glutInit(&argc, argv); 766 glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL); 767 glutCreateWindow("FreeType OpenGL"); 768 init(); 769 glutDisplayFunc(display); 770 glutReshapeFunc(reshape); 771 glutKeyboardFunc(keyboard); 772 glutMainLoop(); 773 }
例子中用到的GLUT和FreeType库请自行配置好。
运行效果:
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库