9
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

freetype2 で文字列描画 (実践編)

Last updated at Posted at 2017-08-23

はじめに

日本語描画が必要だったので、freetypeを使用し実践的なコード組んでみた。
OpenCV 3.2 でFreetype描画がサポートされたようだが、痒い所に手が届かなそう(CalcRectが無いとか)だったので、結局実装してみた。

_72D0559s.jpg

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()
    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互換である。

DrawTextBGRA()

    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`

実装サンプル

Sample

#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;
}

ファイル保存した結果は、以下のようになる。

image.PNG

おわかりいただけただろうか?
世界がαブレンド描画されている。

9
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?