スポンサーリンク

FreeType2で文字のアウトラインを描く

FT_Glyph_StrokeBorderを呼び出して描画する場合に比べ、FT_Glyph_StrokeBorderを呼び出さずに描画した場合のほうが文字が細くなる。

それを利用して、一度太く書いた場所と同じ場所に細く書くことでアウトラインを描画する。

気を付けるのは、FT_Glyph_StrokeBorderあり、なしで文字が入るボックスのサイズが異なってしまうので、top,leftを見て位置合わせが必要になる。

#include <vector>

#include <ft2build.h>
#include FT_FREETYPE_H

#include <freetype/ftstroke.h>

#pragma warning(disable:4996)

#pragma comment(lib,"freetype.lib")


//! @brief PGM(1byte,テキスト)を書き込む
//! @param [in] fname ファイル名
//! @param [in] width 画像の幅
//! @param [in] height 画像の高さ
//! @param [in] p 画像のメモリへのアドレス
//! @details 1画素1Byteのメモリを渡すと、0,1テキストでファイル名fnameで書き込む
void pgmP1_Write(const char* const fname, const int width, const int height, const unsigned char* const p) { // PPM ASCII

    FILE* fp = fopen(fname, "wb");
    fprintf(fp, "P2\n%d %d\n255\n", width, height);

    size_t k = 0;
    for (size_t i = 0; i < (size_t)height; i++) {
        for (size_t j = 0; j < (size_t)width; j++) {
            fprintf(fp, "%d ", p[k] );
            k++;
        }
        fprintf(fp, "\n");
    }

    fclose(fp);
}


struct image_t {
    std::vector<unsigned char> buffer;
    int width;
    int height;
    int top;
    int left;
};

// https://stackoverflow.com/questions/20874056/draw-text-outline-with-freetype

// library FT_Library
// face FT_Face
// character レンダリングする文字のコードポイント
// img 画像の出力先

// thickness アウトラインの太さをピクセルで指定

// is_strokeborder strokeborderを呼ぶかどうか(初回・呼ぶ、二回目・呼ばない)
int draw_stroker(FT_Library& library, FT_Face& face, FT_ULong character, image_t& img,int thickness ,bool is_strokeborder) { // initialize stroker, so you can create outline font FT_Stroker stroker; FT_Stroker_New(library, &stroker); // 2 * 64 result in 2px outline FT_Stroker_Set(stroker, thickness * 64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0); /////////////////////////////////////////////// /////////////////////////////////////////////// // generation of an outline for single glyph: FT_UInt glyphIndex = FT_Get_Char_Index(face, character); FT_Error error = FT_Load_Glyph(face, glyphIndex, FT_LOAD_DEFAULT); if (error) return -1; // ignore errors /////////////////////////////////////////////// /////////////////////////////////////////////// FT_Glyph glyph; FT_Get_Glyph(face->glyph, &glyph); if (is_strokeborder) { FT_Glyph_StrokeBorder(&glyph, stroker, false, true); } FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, nullptr, true); FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(glyph); // blit the glyph here on your target surface. // For positioning use bitmapGlyph->left, bitmapGlyph->top // For iteration over the glyph data use bitmapGlyph->bitmap.buffer, bitmapGlyph->bitmap.width, bitmapGlyph->bitmap.rows, bitmapGlyph->bitmap.pitch. if (is_strokeborder) { img.width = bitmapGlyph->bitmap.width;; img.height = bitmapGlyph->bitmap.rows; img.top = bitmapGlyph->top; img.left = bitmapGlyph->left; for (size_t i = 0; i < img.width * img.height; i++) { // 白黒反転して保存。これで背景が白になる img.buffer.push_back(255 - bitmapGlyph->bitmap.buffer[i]); } } else { // strokeborderしないほうが画像が小さいので画像のサイズが異なる // 左側と上側にオフセットを設けるために位置の差を求める int hw = bitmapGlyph->left - img.left; int hh = img.top - bitmapGlyph->top; int notBorderW = bitmapGlyph->bitmap.width; int notBorderH = bitmapGlyph->bitmap.rows; for (size_t x = 0; x < notBorderW; x++) { for (size_t y = 0; y < notBorderH; y++) { int p = y * notBorderW + x; // strokeborderしないで描かれた領域はくりぬきたいので // その部分を白く塗る。 // しかしグレースケール、アンチエイリアスで描かれているので // ただ255にせず色を足すことでアンチエイリアスを保つ if (bitmapGlyph->bitmap.buffer[p] != 0) { int s = (y+hh) * img.width + (x+hw); img.buffer[s] += bitmapGlyph->bitmap.buffer[p]; } } } } }

      
int main()
{

    FT_Library  library; // handle to library
    FT_Error error;

    error = FT_Init_FreeType(&library);
    if (error)
        return -1;

    FT_Face face;      // handle to face object

    // フォントファイル読み込み
    error = FT_New_Face(
        library,
        "C:\\Windows\\Fonts\\meiryo.ttc",
        0,
        &face
    );

    //文字コード指定
    error = FT_Select_Charmap(
        face,               // target face object
        FT_ENCODING_UNICODE // エンコード指定
    );


    if (error == FT_Err_Unknown_File_Format)
        return -1;
    else if (error)
        return -1;

    //この二つの値でフォントサイズ調整
    FT_F26Dot6 fontsize = 16 * 64*3;
    FT_UInt CHAR_RESOLUTION = 300;
    error = FT_Set_Char_Size(
        face,                // handle to face object
        0,                   // char_width in 1/64th of points
        fontsize,            // char_height in 1/64th of points
        CHAR_RESOLUTION,     // horizontal device resolution
        CHAR_RESOLUTION);    // vertical device resolution

      // 文字の取得
    FT_ULong character = wchar_t(L'あ');


    /////////////////////////////
    image_t img;
    // FT_Glyph_StrokeBorder の呼び出しの有無で二回呼び出す
    draw_stroker(library, face, character,img,5,true);
    draw_stroker(library, face, character, img,5, false);


    int Width = img.width;
    int Height = img.height;
    pgmP1_Write(// ファイル保存
        "C:\\test\\freetypetest.pgm",
        Width,
        Height,
        img.buffer.data()
    );

    // FreeType2の解放
    FT_Done_Face(face);
    FT_Done_FreeType(library);
}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


この記事のトラックバックURL: