今回はパスで描画する
前回はFreeTypeで画像に変換→1画素ずつちまちま描画だったが、そんなことやれませんね(にっこり)ということで、パス描画版も用意しましたり。
ただ、ストロークで描画するフォントがないので、枠線しか描画されない…… M+ の取り組みで昔あったみたいですが、そこはもうちょっとお時間が必要ですね。
パスで描画するメリット・デメリット
- 2値イメージ → 画素ちまちまよりも理論上速いはず(計測してないです)
- 線を太らせたりするのが容易(だけど、もっと細らせたかった)
- 文字内部の塗りつぶしが動きません(Scanline方式を採用してやらないとダメか・・・)
出力結果例
ソースコード
ぐへへ、そうとうえぐいです。
# 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);
}