FreeTypeでOpenCV上に文字を描画しよう

Advent Calendar にするほどでもないので、FreeTypeをOpenCV上で使うサンプルを載せておく。

---
# 材料
1. TrueType Font
2. OpenCV
3. FreeType

今回は、http://mplus-fonts.osdn.jp/ 様の https://ja.osdn.net/projects/mplus-fonts/releases/62344 から mplus-TESTFLIGHT-062.tar.xz を使いました。

---
# したごしらえ
（やらなくてもいい）
フォントデータを毎回ロードするロジックを作ってもいい。
だけど、将来的にOpenCVのライブラリに組み込む前提でライブラリに取り込むことも試作しておこうと思う。

` $ xxd -i mplus-1c-thin.ttf > fontdata.c `

---
# 実行結果

＜修正後＞
![a.png](https://qiita-image-store.s3.amazonaws.com/0/89434/23fb1702-39d4-04a0-8b60-e9df0e1ab849.png)

＜修正前＞
![a.png](https://qiita-image-store.s3.amazonaws.com/0/89434/dc9ae964-3b01-4e2e-2e6c-bf1207de898a.png)

---
# 修正(2016/11/7)

``` 変更差分
        gPos.y = gPos.y - ( mface->glyph->metrics.horiBearingY >> 6) ;
+       gPos.x = gPos.x + ( mface->glyph->metrics.horiBearingX >> 6) ;

-                      ptr[ org.x + col * 8 + (7 - bit) ][0] = color[0];
-                      ptr[ org.x + col * 8 + (7 - bit) ][1] = color[1];
-                      ptr[ org.x + col * 8 + (7 - bit) ][2] = color[2];
+                      ptr[ gPos.x + col * 8 + (7 - bit) ][0] = color[0];
+                      ptr[ gPos.x + col * 8 + (7 - bit) ][1] = color[1];
+                      ptr[ gPos.x + col * 8 + (7 - bit) ][2] = color[2];

-           c  = ( ((unsigned char)text[i  ] << 18 ) & 0x700C0 ) |
+           c  = ( ((unsigned char)text[i  ] << 18 ) & 0x70000 ) |
                 ( ((unsigned char)text[i+1] << 12 ) &  0xF000 ) |
                 ( ((unsigned char)text[i+2] <<  6 ) &  0x0FC0 ) |
                 ( ((unsigned char)text[i+3] <<  0 ) &  0x003F ) ;
```

---
# こんな感じ
``` Makefile
all : a.out

fontdata.cpp.o : fontdata.h fontdata.cpp Makefile
	g++ -c fontdata.cpp -o fontdata.cpp.o

main.cpp.o : fontdata.h main.cpp Makefile
	g++ -g -c main.cpp -o main.cpp.o `pkg-config freetype2 opencv --cflags --libs`

a.out : fontdata.cpp.o main.cpp.o
	g++ fontdata.cpp.o main.cpp.o -o a.out `pkg-config freetype2 opencv --cflags --libs`
```

``` fontdata.h
#ifndef FONTDATA_H
#define FONTDATA
extern unsigned int mplus_1c_thin_ttf_len;
extern unsigned char mplus_1c_thin_ttf[];
#endif
```

``` main.c
#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;

    /** 
     *
     */
    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 ) & 0x70000 ) |
                 ( ((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 ); 
    }
    ~FreeTypeWrapper(){
    // 後処理
    FT_Done_FreeType(mlibrary);
    }

public:
void putText(InputOutputArray _img, const String& text, Point org,
              double fontScale, Scalar color,
              bool bottomLeftOrigin )
{

    if ( text.empty() )
    {
        return;
    }

    FT_Set_Pixel_Sizes( mface, fontScale, fontScale );

    cv::Mat img = _img.getMat();

    for( int i = 0 ; text[i] != '\0' ; ){
        FT_Long c;
        readNextCode(c, i, text );
        FT_Load_Char(mface, c, 0 ); 
        FT_Render_Glyph( mface->glyph, FT_RENDER_MODE_MONO );
        FT_Bitmap    *bmp = &(mface->glyph->bitmap);

        Point gPos = org;
        gPos.y = gPos.y - ( mface->glyph->metrics.horiBearingY >> 6) ;
        gPos.x = gPos.x + ( mface->glyph->metrics.horiBearingX >> 6) ;
        int row, col, bit, cl;

        for (row = 0; row < bmp->rows; row ++) {
            if( gPos.y + row > img.rows ) { continue; }
            cv::Vec3b* ptr = img.ptr<cv::Vec3b>( gPos.y + row );
            for (col = 0; col < bmp->pitch; col ++) {
                cl = bmp->buffer[ row * bmp->pitch + col ];
                for(bit = 7; bit >= 0; bit -- ){
                    if ( gPos.x + col * 8 + (7 - bit) > img.cols ) { continue; }
                    if ( (cl >> bit) & 0x01 == 1 ) {
                       ptr[ gPos.x + col * 8 + (7 - bit) ][0] = color[0];
                       ptr[ gPos.x + col * 8 + (7 - bit) ][1] = color[1];
                       ptr[ gPos.x + col * 8 + (7 - bit) ][2] = color[2];
                    }
                }
            }
        }
        org.x += ( mface->glyph->advance.x ) >> 6;
        org.y += ( mface->glyph->advance.y ) >> 6;
    }
    
}

};
int main()
{
    cv::Mat src = cv::Mat( 480, 640, CV_8UC3 );
    FreeTypeWrapper ftw;
    ftw.putText(src, "ABCDEFGHIJKLMNOPRSTUVWXYZ",  Point(0,16), 16, Scalar(255,255,255), false );
    ftw.putText(src, "ABCDEFGHIJKLMNOPRSTUVWXYZ",  Point(0,40), 24, Scalar(255,255,255), false );
    ftw.putText(src, "ABCDEFGHIJKLMNOPRSTUVWXYZ,", Point(0,64), 32, Scalar(255,255,255), false );
    ftw.putText(src, "いろはにほへと",             Point(0,120), 16, Scalar(255,255,255), false );
    ftw.putText(src, "Happy New Year !!!!!!!!!!",  Point(0,150), 24, Scalar(255,255,255), false );
    ftw.putText(src, "パフォーマンスとかは任せた", Point(0,210), 32, Scalar(255,255,255), false );
    ftw.putText(src, "こんな感じで使えます",       Point(0,250), 32, Scalar(255,255,255), false );
    ftw.putText(src, "赤",                         Point(0,300), 32, Scalar(  0,  0,255), false );
    ftw.putText(src, "青",                         Point(32,300), 32, Scalar(255,  0, 0), false );
    ftw.putText(src, "緑",                         Point(64,300), 32, Scalar( 0, 255, 0), false );
    cv::imwrite("a.png", src);
}
```

``` fontdata.c
#include "fontdata.h"

unsigned char mplus_1c_thin_ttf[] = {
  0x00, 0x01, 0x00, 0x00, 0x00, 0x15, 0x01, 0x00, 0x00, 0x04, 0x00, 0x50,
...
...
...
```

あ、bottomのオプション作るの忘れてた、てへぺろ。