FreeTypeでOpenCV上に文字を"パス"描画する

# 今回はパスで描画する
前回はFreeTypeで画像に変換→１画素ずつちまちま描画だったが、そんなことやれませんね（にっこり）ということで、パス描画版も用意しましたり。

ただ、ストロークで描画するフォントがないので、枠線しか描画されない……　M+ の取り組みで昔あったみたいですが、そこはもうちょっとお時間が必要ですね。

# パスで描画するメリット・デメリット
1. 2値イメージ → 画素ちまちまよりも理論上速いはず（計測してないです）
2. 線を太らせたりするのが容易（だけど、もっと細らせたかった）
3. 文字内部の塗りつぶしが動きません（Scanline方式を採用してやらないとダメか・・・）

# 出力結果例

![a.png](https://qiita-image-store.s3.amazonaws.com/0/89434/76a9c050-4c96-067c-5dff-f1f36b8d3bc9.png)

# ソースコード
ぐへへ、そうとうえぐいです。

```
#include <iostream>
#include <fstream>
#include <string>
#include <cmath>
#include "fontdata.h"
#include <ft2build.h>
#include <freetype/freetype.h>
#include <freetype/ftoutln.h>

#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>

using namespace cv;

class FreeTypeWrapper{
private:
    FT_Library       mlibrary;
    FT_Face          mface;
    FT_Outline_Funcs mFn;

    int              mLine_type;
    int              mThickness;
    int              mHeight;
    Scalar           mColor;
    cv::Mat          mImg;

    FT_Vector        mOldP;
    vector < Point > mPts;

    static const int nCtoL = 16 ; // 直線に変換するときの分割数

    static int ftd(int a){return a >> 6;} // Integer to 26.6 Fixed  Real
public:

    static int mvFn( const FT_Vector *to, void * user)
    {
        FreeTypeWrapper *p = (FreeTypeWrapper *)user;

        if( p->mPts.size() > 0 ){
            const Point *ptsList[] = { &(p->mPts[0]) };
            int npt[1]; npt[0] = p->mPts.size();
            cv::polylines(
                p->mImg,
                ptsList,
                npt,
                1,
                false,
                p->mColor,
                p->mThickness,
                p->mLine_type, 0 );
        }

        if(to == NULL ) { return 1; }
        p->mPts.clear();
        p->mPts.push_back( Point ( ftd(to->x), ftd(to->y) ) );
        p->mOldP = *to;
        return 0;
    }

    static int lnFn( const FT_Vector *to, void * user){
        FreeTypeWrapper *p = (FreeTypeWrapper *)user;
        if(to == NULL ) { return 1; }

        p->mPts.push_back( Point ( ftd(to->x), ftd(to->y) ) );
        p->mOldP = *to;
        return 0;
    }

    static int coFn( const FT_Vector *cnt, const FT_Vector *to, void * user)
    {
        FreeTypeWrapper *p = (FreeTypeWrapper *)user;
        if(cnt == NULL ) { return 1; }
        if(to  == NULL ) { return 1; }

        // Bezier to Line
        for(int i=0;i< nCtoL ;i++){
            float u = (float)i * 1.0 / nCtoL ;
            float p0 =                  ( 1.0 - u ) * (1.0 - u);
            float p1 = 2.0 * u *        ( 1.0 - u ) ;
            float p2 =       u * u;

            float X = (p->mOldP.x) * p0 + cnt->x * p1 + to->x * p2;
            float Y = (p->mOldP.y) * p0 + cnt->y * p1 + to->y * p2;
            p->mPts.push_back( Point ( ftd(X), ftd(Y) ) );
        }
        p->mOldP = *to;
        return 0;
    }

    static int cuFn( const FT_Vector *cnt1, const FT_Vector *cnt2, const FT_Vector *to, void * user)
    {
        FreeTypeWrapper *p = (FreeTypeWrapper *)user;
        if(cnt1 == NULL ) { return 1; }
        if(cnt2 == NULL ) { return 1; }
        if(to   == NULL ) { return 1; }

        // Bezier to Line
        for(int i=0;i< nCtoL ;i++){
            float u = (float)i * 1.0 / nCtoL ;
            float p0 =                  ( 1.0 - u ) * (1.0 - u) * (1.0 - u );
            float p1 = 3.0 * u *        ( 1.0 - u ) * (1.0 - u);
            float p2 = 3.0 * u * u *    ( 1.0 - u );
            float p3 =       u * u * u;

            float X = (p->mOldP.x) * p0 + cnt1->x      * p1 +
                      cnt2->x      * p2 + to->x        * p3;
            float Y = (p->mOldP.y) * p0 + cnt1->y      * p1 +
                      cnt2->y      * p2 + to->y        * p3;

            p->mPts.push_back( Point ( ftd(X), ftd(Y) ) );
        }
        p->mOldP = *to;
        return 0;
    }

    /**
     *
     */
    void readNextCode(FT_Long &c, int &i, const String &text )
    {
        unsigned char t1 = (unsigned char) text[i];
        if( t1 >= 0xF0) //4 bytes utf
        {
            c  = ( ((unsigned char)text[i  ] << 18 ) & 0x700C0 ) |
                 ( ((unsigned char)text[i+1] << 12 ) &  0xF000 ) |
                 ( ((unsigned char)text[i+2] <<  6 ) &  0x0FC0 ) |
                 ( ((unsigned char)text[i+3] <<  0 ) &  0x003F ) ;
            i+=4;
        }
        else if( t1 >= 0xE0) //3 bytes utf
        {
            c  = ( ((unsigned char)text[i  ] << 12 ) & 0xF000 ) |
                 ( ((unsigned char)text[i+1] <<  6 ) & 0x0FC0 ) |
                 ( ((unsigned char)text[i+2] <<  0 ) & 0x003F ) ;
            i+=3;
        }
        else if( t1 >= 0xC2) //2 bytes utf
        {
            c  = ( ((unsigned char)text[i  ] <<  6 ) & 0x0FC0 ) |
                 ( ((unsigned char)text[i+1] <<  0 ) & 0x003F ) ;
            i+=2;
        }
        else if(t1 > 0 )//1 bytes utf
        {
            c  = text[i];
            i+=1;
        }else{
            c = '?';
            i++;
        }
    }

public:
    FreeTypeWrapper(){
        FT_Init_FreeType(&mlibrary);
        FT_New_Memory_Face(mlibrary,
                       mplus_1c_thin_ttf,
                       mplus_1c_thin_ttf_len, 0, &mface );
        mFn.shift = 0;
        mFn.move_to  = FreeTypeWrapper::mvFn;
        mFn.line_to  = FreeTypeWrapper::lnFn;
        mFn.cubic_to = FreeTypeWrapper::cuFn;
        mFn.conic_to = FreeTypeWrapper::coFn;
    }

    ~FreeTypeWrapper(){
        FT_Done_FreeType(mlibrary);
    }

public:
void putText(InputOutputArray _img, const String& text, Point org,
              int fontScale, Scalar _color,
              int _thickness, int _line_type, bool bottomLeftOrigin )
{
    if ( text.empty() )
    {
        return;
    }

    FT_Set_Pixel_Sizes( mface, fontScale, fontScale );

    if( _line_type == CV_AA && _img.depth() != CV_8U ){
        _line_type = 8;
    }

    mThickness = _thickness;
    mLine_type = _line_type;
    mColor     = _color;
    mImg       = _img.getMat();
    mHeight    =  fontScale;
    mPts.clear();

    if( bottomLeftOrigin ) {
        org.y -= mHeight;
    }

    for( int i = 0 ; text[i] != '\0' ; ){
        FT_Long c;
        readNextCode(c, i, text );

        FT_Load_Char(mface, c, FT_LOAD_DEFAULT );
        FT_GlyphSlot slot = mface->glyph;
        FT_Outline outline = slot->outline;

        // Image is flipped.
        FT_Matrix mtx = { 1 << 16 , 0 , 0 , -(1 << 16) };
        FT_Outline_Transform(&outline, &mtx);

        // Move
        FT_Outline_Translate(&outline,
                             (FT_Pos)(org.x << 6),
                             (FT_Pos)((org.y + mHeight)  << 6) );

        // Draw
        FT_Outline_Decompose(&outline, &mFn, (void*)this);

        // 最後にデータが残っていたら出力
        mvFn( NULL, (void*)this );

        org.x += ( mface->glyph->advance.x ) >> 6;
        org.y += ( mface->glyph->advance.y ) >> 6;
    }

}

};
int main()
{
    cv::Mat src = cv::Mat::zeros( 480, 640, CV_8UC3 );
    FreeTypeWrapper ftw;
    ftw.putText(src, "ABCDEFGHIJKLNMNOPQRSTUVWXYZ",  Point(0,0), 16, Scalar(0,0,255), 1, 8, false );
    ftw.putText(src, "ABCDEFGHIJKLNMNOPQRSTUVWXYZ",  Point(0,20), 24, Scalar(0,0,255), 1, 8, false );
    ftw.putText(src, "ABCDEFGHIJKLNMNOPQRSTUVWXYZ",  Point(0,48), 32, Scalar(0,0,255), 1, 8, false );
    ftw.putText(src, "ABCDEFGHIJKLNMNOPQRSTUVWXYZ",  Point(0,74), 64, Scalar(0,0,255), 1, 8, false );
    ftw.putText(src, "Aliasing(4)",  Point(0,200), 64, Scalar(0,0,255), 1, 4 , true );
    ftw.putText(src, "Aliasing(8)",  Point(0,272), 64, Scalar(0,0,255), 1, 8 , true );
    ftw.putText(src, "AntiAliasing",  Point(0,344), 64, Scalar(0,0,255), 1, CV_AA , true );
    ftw.putText(src, "内部塗りつぶしはNG!",  Point(400,240), 16, Scalar(255,255,255), 1, CV_AA , true );
    ftw.putText(src, "寿限無寿限無",  Point(400,344), 16, Scalar(255,255,255), 1, CV_AA , true );
    ftw.putText(src, "寿限無寿限無",  Point(400,380), 16, Scalar(255,255,255), 1, 1 , true );
    cv::imwrite("a.png", src);
} 
```