はじめに
日本語描画が必要だったので、freetypeを使用し実践的なコード組んでみた。
OpenCV 3.2 でFreetype描画がサポートされたようだが、痒い所に手が届かなそう(CalcRectが無いとか)だったので、結局実装してみた。
Freetype のAPI referenceなど
API reference
https://www.freetype.org/freetype2/docs/reference/ft2-index.html
Tutorial
https://www.freetype.org/freetype2/docs/tutorial/step1.html
https://www.freetype.org/freetype2/docs/tutorial/step2.html
Sample
https://www.freetype.org/freetype2/docs/tutorial/example1.c
https://www.freetype.org/freetype2/docs/tutorial/example2.cpp
作成コード
全体はgitで。
https://github.com/blue777/NanoPi-NEO/blob/master/common/img_font.h
クラス
クラス名:ImageFont
メソッド | 概要 |
---|---|
ImageFont( const char* filename, uint32_t height, bool isFitHeight=true ) | コンストラクタ |
CalcRect( int& left, int& top, int& right, int& bottom, const char* str ) | 文字列の描画サイズ計算 |
DrawTextGRAY( int x, int y, const char* str, uint8_t color, uint8_t * image, int stride, int cx, int cy ) | Grayバッファへの文字列描画 |
DrawTextBGRA( int x, int y, const char* str, uint32_t color, uint8_t * image, int stride, int cx, int cy ) | BGRAバッファへの文字列描画(α値指定描画対応) |
コンストラクタ
基本的な初期化を行う。
指定した高さからはみ出て描画(baseline管理になるので、gjpqyなど基準線より下の部分)されるので、はみ出ないように調整する機能も付けている。
FT_Library m_piLibrary;
FT_Face m_piFace;
int m_nBaseline;
ImageFont( const char* filename, uint32_t height, bool isFitHeight=true )
{
FT_Error error;
FT_Size_RequestRec tReqSize;
// initialize member var.
m_nBaseline = height;
m_piLibrary = NULL;
m_piFace = NULL;
error = FT_Init_FreeType( &m_piLibrary );
if( 0 != error )
{
throw "ERROR: FT_Init_FreeType()";
}
error = FT_New_Face( m_piLibrary, filename, 0, &m_piFace );
if( 0 != error )
{
throw "ERROR: FT_New_Face";
}
tReqSize.type = FT_SIZE_REQUEST_TYPE_NOMINAL;
tReqSize.width = 0;
tReqSize.height = (height << 6);
tReqSize.horiResolution = 0;
tReqSize.vertResolution = 0;
if( isFitHeight )
{
// b. Scaled Global Metrics
int yMax = m_piFace->bbox.yMax;
int yMin = m_piFace->bbox.yMin;
m_nBaseline = height * yMax / (yMax - yMin);
tReqSize.type = FT_SIZE_REQUEST_TYPE_BBOX;
tReqSize.height = (height << 6);
}
error = FT_Request_Size( m_piFace, &tReqSize );
if( 0 != error )
{
throw "ERROR: FT_Request_Size()";
}
}
BGRA描画部
Freetypeに渡す際には、UNICODE32で1文字ずつ渡し描画する必要がある。
colorの上位8bitはα値で、文字列の半透明描画も出来るようにしてみている。
なお、BGRAは、OpenCVのCV_8UC4互換である。
int DrawTextBGRA( int x, int y, const char* str, uint32_t color, uint8_t * image, int stride, int cx, int cy )
{
std::u32string u32str = std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t>().from_bytes(str);
return DrawTextBGRA( x, y, u32str, color, image, stride, cx, cy );
}
int DrawTextBGRA( int x, int y, const std::u32string& u32str, uint32_t color, uint8_t * image, int stride, int cx, int cy )
{
FT_Error error;
FT_GlyphSlot slot = m_piFace->glyph;
FT_Matrix matrix = { 1 << 16, 0, 0, 1 << 16 };
FT_Vector pen = { 0, 0 };
uint32_t alpha = (color >> 24) + (color >> 31);
for( size_t i = 0; i < u32str.size(); i++ )
{
switch( u32str[i] )
{
case '\r':
break;
case '\t':
pen.x += m_piFace->size->metrics.max_advance * 4;
pen.x -= pen.x % (m_piFace->size->metrics.max_advance * 4);
break;
case '\n':
pen.x = 0;
pen.y -= m_piFace->size->metrics.height;
break;
default:
FT_Set_Transform( m_piFace, &matrix, &pen );
error = FT_Load_Char( m_piFace, u32str[i], FT_LOAD_RENDER );
if( 0 == error )
{
int bmp_cy = slot->bitmap.rows;
int pos_y = y + m_nBaseline - slot->bitmap_top;
int rs = 0 <= pos_y ? 0 : -pos_y;
int re = (pos_y + bmp_cy) <= cy ? bmp_cy : (cy - pos_y);
int bmp_cx = slot->bitmap.width;
int pos_x = x + slot->bitmap_left;
int cs = 0 <= pos_x ? 0 : -pos_x;
int ce = (pos_x + bmp_cx) <= cx ? bmp_cx : (cx - pos_x);
for( int r = rs; r < re; r++ )
{
uint8_t * src_line = &slot->bitmap.buffer[ bmp_cx * r ];
uint8_t * dst_line = &image[ stride * (pos_y + r) + (pos_x * 4) ];
int c = cs;
for( ; c < ce; c++ )
{
int32_t a = src_line[c+0];
if( 0 < a )
{
uint8_t* clr = (uint8_t*)&color;
int32_t d0 = dst_line[c*4+0];
int32_t d1 = dst_line[c*4+1];
int32_t d2 = dst_line[c*4+2];
int32_t d3 = dst_line[c*4+3];
a += a >> 7;
a *= alpha;
dst_line[c*4+0] = (uint8_t)( ((d0 << 16) + (clr[0] - d0) * a) >> 16 );
dst_line[c*4+1] = (uint8_t)( ((d1 << 16) + (clr[1] - d1) * a) >> 16 );
dst_line[c*4+2] = (uint8_t)( ((d2 << 16) + (clr[2] - d2) * a) >> 16 );
dst_line[c*4+3] = (uint8_t)( ((d3 << 16) + (clr[3] - d3) * a) >> 16 );
}
}
}
pen.x += slot->advance.x;
pen.y += slot->advance.y;
}
break;
}
}
return 0;
}
呼び出しサンプル
最初写真は、この実行結果になります。
UTF-8をUNICODE32に変換して、Freetypeでbitmap化して、αブレンドしながら描画し、OLEDに表示しています。
各種ディスプレイの接続などは、NanoPi-NEOと5種のLCDと2種のOLED にて。
コンパイルコマンド例:
g++ -O3 -std=c++11 Sample.cpp -o Sample.o `freetype-config --cflags` `freetype-config --libs`
実装サンプル
#include "common/img_font.h"
#include "common/display_ssd1306_i2c.h"
#include "common/display_st7735_spi.h"
#include "common/display_ili9225_spi.h"
#include "common/display_ili9328_spi.h"
#include "common/display_ili9341_spi.h"
#define FONT_PATH "/usr/share/fonts/truetype/takao-gothic/TakaoPGothic.ttf"
int main()
{
const int buf_width = 320;
const int buf_height = 240;
const int buf_stride = buf_width * 4;
uint8_t buf[buf_width*buf_height*4];
std::vector<DisplayIF*> display;
// display.push_back( new Display_ILI9225_spi(270) );
// display.push_back( new Display_ILI9341_spi_TM24(270) );
// display.push_back( new Display_ILI9341_spi_TM22(270) );
// display.push_back( new Display_ILI9341_spi(270) );
// display.push_back( new Display_ILI9328_spi_TM22(270) );
// display.push_back( new Display_ST7735_spi(270) );
display.push_back( new Display_SSD1306_i2c(180) );
for( auto it : display )
{
it->Init();
it->DispOn();
it->DispClear();
}
ImageFont iFont1( FONT_PATH, 32 );
ImageFont iFont2( FONT_PATH, 64 );
const char* pszText1 = u8"こんにちわ";
const char* pszText2 = u8"世界";
int cx, cy;
memset( buf, 0, sizeof(buf) );
iFont1.DrawTextBGRA( 0, 0, pszText1, 0xFFFFFFFF, buf, buf_stride, buf_width, buf_height );
iFont2.DrawTextBGRA( 16, 8, pszText2, 0xA000FF00, buf, buf_stride, buf_width, buf_height );
for( auto it : display )
{
it->WriteImageBGRA( 0, 0, buf, buf_stride, buf_width, buf_height );
}
FILE* fp = fopen("image.bin", "wb");
if( fp != NULL )
{
fwrite( buf, buf_stride, buf_height, fp );
fclose( fp );
}
getchar();
return 0;
}
ファイル保存した結果は、以下のようになる。
おわかりいただけただろうか?
世界がαブレンド描画されている。