LoginSignup
9
8

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-10-31

今回はパスで描画する

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

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

パスで描画するメリット・デメリット

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

出力結果例

a.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);
} 
9
8
3

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
8