Skip to content

Instantly share code, notes, and snippets.

@x5lcfd
Created April 21, 2016 14:01
Show Gist options
  • Save x5lcfd/c196787c49ec14f628df472f7b41df75 to your computer and use it in GitHub Desktop.
Save x5lcfd/c196787c49ec14f628df472f7b41df75 to your computer and use it in GitHub Desktop.
FreeType Render UTF8 string
/* 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