Created
April 21, 2016 14:01
-
-
Save x5lcfd/c196787c49ec14f628df472f7b41df75 to your computer and use it in GitHub Desktop.
FreeType Render UTF8 string
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Compile & Run: | |
clang++ -std=c++11 -fsanitize=address `freetype-config --cflags` `icu-config --cflags | sed -e 's/-std=c99//g' | sed -e 's/-O2//g'` -o freetype_test freetype_test.cpp `freetype-config --libs` `icu-config --ldflags` -lopencv_core -lopencv_highgui && ASAN_OPTIONS="detect_leaks=1" ./freetype_test | |
*/ | |
// Change the following to suit your need. | |
static const char *FONT_FILE = "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc"; | |
static const char *STR = u8"哈哈abcdefghijklmnopqrstuvwxyz1234567890"; | |
static const int PIXEL_SIZE = 32, COLOR_BLUE = 255, COLOR_GREEN = 255, COLOR_RED = 0; | |
#include <cstdio> | |
#include <cstring> | |
// FreeType headers | |
#include <ft2build.h> | |
#include FT_FREETYPE_H | |
// ICU headers | |
#include <unicode/ustring.h> | |
// OpenCV headers | |
#include <opencv2/core/core.hpp> | |
#include <opencv2/highgui/highgui.hpp> | |
#include <memory> | |
#include <functional> | |
class ScopeExit { | |
public: | |
ScopeExit(std::function<void()> f): f_(f) {} | |
~ScopeExit() { f_(); } | |
private: | |
std::function<void()> f_; | |
}; | |
int main() { | |
// Initialize FreeType | |
FT_Library ft_library; | |
int ret = FT_Init_FreeType(&ft_library); | |
if (ret != 0) { | |
fprintf(stderr, "FT_Init_FreeType() failed.\n"); | |
return 0; | |
} | |
ScopeExit ft_library_done([&ft_library] () { | |
FT_Done_FreeType(ft_library); | |
}); | |
FT_Face ft_face; | |
ret = FT_New_Face(ft_library, FONT_FILE, 0, &ft_face); | |
if (ret != 0) { | |
fprintf(stderr, "FT_New_Face() failed.\n"); | |
return 0; | |
} | |
ScopeExit ft_face_done([&ft_face] () { | |
FT_Done_Face(ft_face); | |
}); | |
ret = FT_Set_Pixel_Sizes(ft_face, PIXEL_SIZE, PIXEL_SIZE); | |
if (ret != 0) { | |
fprintf(stderr, "FT_Set_Pixel_Sizes() failed.\n"); | |
return 0; | |
} | |
// FreeType uses Unicode as glyph index; so we have to convert string from UTF8 to Unicode(UTF32) | |
int utf16_buf_size = strlen(STR) + 1; // +1 for the last '\0' | |
std::unique_ptr<UChar[]> utf16_str(new UChar[utf16_buf_size]); | |
UErrorCode err = U_ZERO_ERROR; | |
int utf16_length; | |
u_strFromUTF8(utf16_str.get(), utf16_buf_size, &utf16_length, STR, strlen(STR), &err); | |
if (err != U_ZERO_ERROR) { | |
fprintf(stderr, "u_strFromUTF8() failed: %s\n", u_errorName(err)); | |
return 0; | |
} | |
int utf32_buf_size = utf16_length + 1; // +1 for the last '\0' | |
std::unique_ptr<UChar32[]> utf32_str(new UChar32[utf32_buf_size]); | |
int utf32_length; | |
u_strToUTF32(utf32_str.get(), utf32_buf_size, &utf32_length, utf16_str.get(), utf16_length, &err); | |
if (err != U_ZERO_ERROR) { | |
fprintf(stderr, "u_strToUTF32() failed: %s\n", u_errorName(err)); | |
return 0; | |
} | |
// Get total width | |
int total_width = 0; | |
int max_height = 0; | |
for (int i = 0; i < utf32_length; i++) { | |
FT_UInt glyph_index = FT_Get_Char_Index(ft_face, utf32_str[i]); | |
// Have to use FT_LOAD_RENDER. | |
// If use FT_LOAD_DEFAULT, the actual glyph bitmap won't be loaded, | |
// thus bitmap->rows will be incorrect, causing insufficient max_height. | |
ret = FT_Load_Glyph(ft_face, glyph_index, FT_LOAD_RENDER); | |
if (ret != 0) { | |
fprintf(stderr, "FT_Load_Glyph() failed.\n"); | |
return 0; | |
} | |
total_width += (ft_face->glyph->advance.x >> 6); | |
FT_Bitmap *bitmap = &(ft_face->glyph->bitmap); | |
int top = (ft_face->ascender >> 6) - ft_face->glyph->bitmap_top; | |
if (top < 0) | |
top = 0; | |
max_height = std::max(max_height, top + bitmap->rows); | |
} | |
// Copy grayscale image from FreeType to OpenCV | |
cv::Mat gray(max_height, total_width, CV_8UC1, cv::Scalar::all(0)); | |
int x = 0; | |
for (int i = 0; i < utf32_length; i++) { | |
FT_UInt glyph_index = FT_Get_Char_Index(ft_face, utf32_str[i]); | |
ret = FT_Load_Glyph(ft_face, glyph_index, FT_LOAD_RENDER); | |
if (ret != 0) { | |
fprintf(stderr, "FT_Load_Glyph() failed.\n"); | |
return 0; | |
} | |
FT_Bitmap *bitmap = &(ft_face->glyph->bitmap); | |
cv::Mat glyph_img(bitmap->rows, bitmap->width, CV_8UC1, bitmap->buffer, bitmap->pitch); | |
int top = (ft_face->ascender >> 6) - ft_face->glyph->bitmap_top; | |
if (top < 0) | |
top = 0; | |
cv::Mat gray_part(gray, cv::Rect(x + ft_face->glyph->bitmap_left, top, bitmap->width, bitmap->rows)); | |
glyph_img.copyTo(gray_part); | |
x += (ft_face->glyph->advance.x >> 6); | |
} | |
// Merge. The grayscale image act as an alpha channel. | |
cv::Mat b(gray.size(), CV_8UC1, cv::Scalar::all(COLOR_BLUE)); | |
cv::Mat g(gray.size(), CV_8UC1, cv::Scalar::all(COLOR_GREEN)); | |
cv::Mat r(gray.size(), CV_8UC1, cv::Scalar::all(COLOR_RED)); | |
for (int i = 0; i < b.rows; i++) { | |
for (int j = 0; j < b.cols; j++) { | |
double alpha = (double)gray.at<uint8_t>(i, j) / 255; | |
b.at<uint8_t>(i, j) *= alpha; | |
g.at<uint8_t>(i, j) *= alpha; | |
r.at<uint8_t>(i, j) *= alpha; | |
} | |
} | |
std::vector<cv::Mat> channels = {b, g, r}; | |
cv::Mat result; | |
cv::merge(channels, result); | |
// Show the image | |
cv::imshow("image", result); | |
cv::waitKey(0); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment