Created
January 10, 2019 09:03
-
-
Save czyang/6df4d841f997e63305bda49ab1eadd49 to your computer and use it in GitHub Desktop.
FreeType Render UTF8 string
This file contains hidden or 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