FreeTypeを使う 新しいページはコチラ

提供: yonewiki
移動: 案内, 検索
(サンプル6.公式サイトのStatic Jobs LLC作成)
 
417行: 417行:
  
  
<Syntaxhighlight2 lang="cpp" line="107">
+
<Syntaxhighlight2 lang="cpp" line start="107">
 
         idces[i] = FT_Get_Char_Index(face, text[i]);
 
         idces[i] = FT_Get_Char_Index(face, text[i]);
 
</Syntaxhighlight2>
 
</Syntaxhighlight2>
428行: 428行:
  
  
<Syntaxhighlight2 lang="cpp" line="109">
+
<Syntaxhighlight2 lang="cpp" line start="109">
 
         error = FT_Load_Glyph(face, idces[i], FT_LOAD_DEFAULT);
 
         error = FT_Load_Glyph(face, idces[i], FT_LOAD_DEFAULT);
 
</Syntaxhighlight2>
 
</Syntaxhighlight2>
438行: 438行:
 
 それが115行目だ。
 
 それが115行目だ。
  
<Syntaxhighlight2 lang="cpp" line="115">
+
<Syntaxhighlight2 lang="cpp" line start="115">
 
         error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
 
         error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
 
</Syntaxhighlight2>
 
</Syntaxhighlight2>
671行: 671行:
 
 freetypeで独自のフォントの変形処理を作るのが醍醐味なのかもしれません。ここまでのチュートリアルがわかっただけでも、相当いろいろな変形処理が作れそう。任意の半径の丸い円弧状にそって文字を配置したりもできるようになりそうです。出来栄えを確かめたりする作業をもった素早くするには、それなりのユーザインタフェースを作らないと駄目ですね。ややこしそう。画像をあやつる一歩を踏み出した感じ。いいね。この例にも文字を変形させる処理があります。FT_Set_Transformという関数とそのちょっとまえのFT_Matrixというクラス変数のmatrixというオブジェクトの準備作業が必要なようです。angleの計算の割り算の優先度を上げているところでカッコの中の数字のどちらかに小数点がないと、なにをやっても計算結果が整数の 0 になって、angleは常に 0 になってしまいます。注意されたし。
 
 freetypeで独自のフォントの変形処理を作るのが醍醐味なのかもしれません。ここまでのチュートリアルがわかっただけでも、相当いろいろな変形処理が作れそう。任意の半径の丸い円弧状にそって文字を配置したりもできるようになりそうです。出来栄えを確かめたりする作業をもった素早くするには、それなりのユーザインタフェースを作らないと駄目ですね。ややこしそう。画像をあやつる一歩を踏み出した感じ。いいね。この例にも文字を変形させる処理があります。FT_Set_Transformという関数とそのちょっとまえのFT_Matrixというクラス変数のmatrixというオブジェクトの準備作業が必要なようです。angleの計算の割り算の優先度を上げているところでカッコの中の数字のどちらかに小数点がないと、なにをやっても計算結果が整数の 0 になって、angleは常に 0 になってしまいます。注意されたし。
  
https://subscription.packtpub.com/book/game-development/9781782167785/2/ch02lvl1sec28/using-the-freetype-library-for-text-rendering
+
 
 +
 グリフ毎にある要素の意味は以下のサイトで画像付きで表記されていて、わかりやすいですね。これがglyph->advancedの意味かぁとかね。
 +
 
 +
[https://subscription.packtpub.com/book/game-development/9781782167785/2/ch02lvl1sec28/using-the-freetype-library-for-text-rendering https://subscription.packtpub.com/book/game-development/9781782167785/2/ch02lvl1sec28/using-the-freetype-library-for-text-rendering]
 
 
 
 
 +
 +
 もうちょっとあともういくつか公式のサンプルがあるので理解をすすめてみたいと思います。
 +
 +
 +
=== ''' サンプル3.公式サイトのErik Mollerさん作成 Qt版 ''' ===
 +
 エリックさんが作ったBという文字をアウトライン装飾ありで、文字の色とアウトラインの色を指定してレンダリングするというものです。ここまで手をかけておきながら、Bだけの固定のサンプルになっているところは、なかなか尖ったサンプルです。縁取りはこうやるということが伝わればいいわけで、色々な形をみれるという汎用性はバサッと切り捨て。カッコいい。
 +
 +
 +
 出力がTGAファイルなのもイカす。TGAはTruVision社が作った。Graphic形式です。ちょっと改造しないとVisual Studioではヘッダー部分が壊れてしまう感じでした。具体的にいうと、ヘッダーで使われているunsigned int の uint16 だと4バイトになってしまいます。2バイトでなければならない情報がすべて4バイトになって位置ズレをおこしてしまいます。
 +
 +
 +
 出力された画像はirfanviewとかで見るのがいいんじゃないかな。それだと面倒だと感じる人もいるかもしれないので、bitmapも出力するようにTGAを出力する次の行に追加して変更しておきました。
 +
 +
 +
<Syntaxhighlight2 lang="cpp" line="1">
 +
#include <QtCore/QCoreApplication>
 +
 +
#include <QObject>
 +
 +
#include <QString>
 +
#include <QFile>
 +
#include <QTextStream>
 +
#include <QImage>
 +
#include <QColor>
 +
#include <QSize>
 +
 +
#pragma execution_character_set("utf-8")
 +
 +
#include <ft2build.h>
 +
#include FT_FREETYPE_H
 +
#include FT_STROKER_H
 +
 +
#include <vector>
 +
#include <fstream>
 +
#include <iostream>
 +
 +
#ifdef _MSC_VER
 +
#define MIN __min
 +
#define MAX __max
 +
#else
 +
#define MIN std::min
 +
#define MAX std::max
 +
#endif
 +
 +
typedef unsigned char uint8;
 +
typedef unsigned uint16;
 +
typedef unsigned int uint32;
 +
 +
#if (('1234' >> 24) ==  '1')
 +
//0x31 0x32 0x33 0x34 >> 24 = 0x00 0x00 0x00 0x31 '1'
 +
// or
 +
//0x34 0x33 0x32 0x31 >> 24 = 0x00 0x00 0x00 0x34 '4'
 +
//BIG ENDIAN方式なら下の動きなのif文は否定。
 +
#elif (('4321' >> 24) == '1')
 +
  #define BIG_ENDIAN
 +
#else
 +
#error "Couldn't determine the endianness!"
 +
#endif
 +
 +
#define WIDTH 640
 +
#define HEIGHT 480
 +
#define LEFT 32
 +
#define TOP 32
 +
 +
static unsigned char canvas[HEIGHT][WIDTH];
 +
 +
 +
union Pixel32 {
 +
    Pixel32() : integer(0) {}
 +
    Pixel32(uint8 bi, uint8 gi, uint ri, uint ai = 255) {
 +
        b = bi;
 +
        g = gi;
 +
        r = ri;
 +
        a = ai;
 +
    } uint32 integer;
 +
    struct{
 +
#ifdef BIG_ENDIAN
 +
        uint8 a, r, g, b;
 +
#else
 +
        uint8 b, g, r, a;
 +
#endif
 +
    };
 +
};
 +
 +
struct Vec2 {
 +
    Vec2() {}
 +
    Vec2(float a, float b) : x(a), y(b) {}
 +
    float x, y;
 +
};
 +
 +
struct Rect {
 +
    Rect() {}
 +
    Rect(float left, float top, float right, float bottom) : xmin(left), xmax(right), ymin(top), ymax(bottom){}
 +
 +
    void Include(const Vec2& r) {
 +
        xmin = MIN(xmin, r.x);
 +
        ymin = MIN(ymin, r.y);
 +
        xmax = MAX(xmax, r.x);
 +
        ymax = MAX(ymax, r.y);
 +
    }
 +
    float Width() const { return xmax - xmin + 1; }
 +
    float Height() const { return ymax - ymin + 1; }
 +
    float xmin, xmax, ymin, ymax;
 +
};
 +
 +
#if defined(_MSC_VER) || defined(__GNUC__)
 +
#pragma pack(push, 1)
 +
#pragma pack(1)
 +
#endif
 +
 +
struct TGAHeader
 +
{
 +
    uint8 idLength, paletteType, imageType;
 +
    unsigned short firstPaletteEntry, numPaletteEntries;
 +
//    uint16 firstPaletteEntry, numPaletteEntries;
 +
    uint8 paletteBits;
 +
    unsigned short x, y, width, height;
 +
//    uint16 x, y, width, height;
 +
    uint8 depth, descriptor;
 +
};
 +
 +
#if defined(_MSC_VER) || defined(__GNUC__)
 +
#pragma pack(pop)
 +
#endif
 +
 +
bool WriteTGA(const std::string& filename, const Pixel32* pxl, uint16 width, uint16 height) {
 +
    std::ofstream file(filename.c_str(), std::ios::binary);
 +
    if (file) {
 +
        TGAHeader header;
 +
        memset(&header, 0, sizeof(TGAHeader));
 +
        //Header部の長さを指定。指定がない場合は固定の長さ。
 +
        header.idLength = 0; // 1byte 0~255
 +
 +
        //Color Map 0★=無, 1=有(この場合0~127はTruVisionが予約している色、128~255はファイルで定義)
 +
        header.paletteType = 0; // 1byte
 +
 +
        //TGAファイルの形式
 +
        //0- 画像データは含まれていません。
 +
        //1- 非圧縮、カラーマップ画像
 +
        //2★- 非圧縮、トゥルーカラー画像
 +
        //3- 非圧縮、白黒画像   
 +
        //9- ランレングスエンコード、カラーマップ画像 
 +
        //10- ランレングスエンコード、トゥルーカラー画像
 +
        //11-ランレングスエンコード、白黒画像
 +
        header.imageType = 2; // 1byte
 +
 +
        //カラーマップ///////////////////////////////
 +
        //カラーマップを使う場合の最初の番号
 +
        header.firstPaletteEntry = 0; // 2byte
 +
 +
        //カラーマップを使う場合の総数
 +
        header.numPaletteEntries = 0; // 2byte
 +
 +
        //カラーマップを使う場合の色を表すのに使うビット数。専用カードがある場合でも変わる。
 +
        header.paletteBits = 0; // 1byte
 +
        //////////////////////////////////////////////
 +
 +
        //画像原点位置/////////////////////////////
 +
        //左下原点としたときの画像開始水平位置
 +
        header.x = 0; // 1byte
 +
 +
        //左下原点としたときの画像開始垂直位置
 +
        header.y = 0; // 1byte
 +
        //////////////////////////////////////////////
 +
 +
        //画像サイズ///////////////////////////////
 +
        //横幅
 +
        header.width = width; // 2byte
 +
 +
        //縦幅
 +
        header.height = height; // 2byte
 +
        //////////////////////////////////////////////
 +
 +
        //色表現bit数
 +
        header.depth = 32; // 1byte
 +
 +
        //ヘッダ説明部 ビットによって細分化されている
 +
        //下位 0~3bit 1ピクセル毎のアルファチャンネルビット数
 +
        //上位 4bit 1なら右から左へ描画
 +
        //上位 5bit 1なら上から下へ描画
 +
        //上位 6~7bit 未使用 通常は00で固定
 +
        header.descriptor = 0x20; // = 0b0010 0000 1byte
 +
 +
        file.write((const char*)&header, sizeof(TGAHeader));
 +
        file.write((const char*)pxl, sizeof(Pixel32) * width * height);
 +
 +
        return true;
 +
    }
 +
    return false;
 +
}
 +
 +
struct Span{
 +
    Span() {}
 +
    Span(int _x, int _y, int _width, int _coverage) : x(_x), y(_y), width(_width), coverage(_coverage) {}
 +
 +
    int x, y, width, coverage;
 +
};
 +
 +
typedef std::vector<Span> Spans;
 +
 +
void RasterCallback(const int y, const int count, const FT_Span* const spans, void* const user) {
 +
    Spans* sptr = (Spans*)user;
 +
    for (int i = 0; i < count; i++) {
 +
        sptr->push_back(Span(spans[i].x, y, spans[i].len, spans[i].coverage));
 +
    }
 +
}
 +
 +
void RenderSpans(FT_Library& library, FT_Outline* const outline, Spans* spans) {
 +
    FT_Raster_Params params;
 +
    memset(&params, 0, sizeof(params));
 +
    params.flags = FT_RASTER_FLAG_AA | FT_RASTER_FLAG_DIRECT;
 +
    params.gray_spans = RasterCallback;
 +
    params.user = spans;
 +
    FT_Outline_Render(library, outline, &params);
 +
}
 +
 +
void WriteBMP(const std::string& filename, const Pixel32* pxl, uint16 width, uint16 height) {
 +
    QSize QSize_xy(width, height);
 +
    QString QString_ImageFileName = filename.data();
 +
    QImage QImage_Canvas(QSize_xy, QImage::Format_ARGB32_Premultiplied);
 +
    int i = 0;
 +
 +
    for (int h = 0; h < height; ++h) {
 +
        for (int w = 0; w < width; ++w) {
 +
 +
            Pixel32 dst = pxl[(int)(width * h  + w)];
 +
            QImage_Canvas.setPixel(w, h, qRgba(dst.r, dst.g, dst.b, dst.a));
 +
 +
        }
 +
    }
 +
    QImage_Canvas.save(QString_ImageFileName, "BMP");
 +
}
 +
 +
void WriteGlyphAsTGA(FT_Library& library, const std::string& fileName, wchar_t ch, FT_Face& face, int size,
 +
    const Pixel32& fontCol, const Pixel32 outlineCol, float outlineWidth) {
 +
    if (FT_Set_Char_Size(face, size << 6, size << 6, 90, 90) == 0){
 +
        FT_UInt gindex = FT_Get_Char_Index(face, ch);
 +
        if (FT_Load_Glyph(face, gindex, FT_LOAD_NO_BITMAP) == 0 ){
 +
            if (face->glyph->format == FT_GLYPH_FORMAT_OUTLINE) {
 +
                Spans spans;
 +
                RenderSpans(library, &face->glyph->outline, &spans);
 +
 +
                Spans outlineSpans;
 +
 +
                FT_Stroker stroker;
 +
                FT_Stroker_New(library, &stroker);
 +
                FT_Stroker_Set(stroker, (int)(outlineWidth * 64), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
 +
                FT_Glyph glyph;
 +
 +
                if (FT_Get_Glyph(face->glyph, &glyph) == 0) {
 +
                    FT_Glyph_StrokeBorder(&glyph, stroker, 0, 1);
 +
                    if (glyph->format == FT_GLYPH_FORMAT_OUTLINE) {
 +
                        FT_Outline* o = &reinterpret_cast<FT_OutlineGlyph>(glyph)->outline;
 +
                        RenderSpans(library, o, &outlineSpans);
 +
                    }
 +
                    FT_Stroker_Done(stroker);
 +
                    FT_Done_Glyph(glyph);
 +
 +
                    if (!spans.empty()) {
 +
                        Rect rect(spans.front().x, spans.front().y, spans.front().x, spans.front().y);
 +
                        for (Spans::iterator s = spans.begin(); s != spans.end(); ++s) {
 +
                            rect.Include(Vec2(s->x, s->y));
 +
                            rect.Include(Vec2(s->x + s->width - 1, s->y));
 +
                        }
 +
                        for (Spans::iterator s = outlineSpans.begin(); s != outlineSpans.end(); ++s) {
 +
                            rect.Include(Vec2(s->x, s->y));
 +
                            rect.Include(Vec2(s->x + s->width - 1, s->y));
 +
                        }
 +
#if 0
 +
                        float bearingX = face->glyph->metrics.horiBearingX >> 6;
 +
                        float bearingY = face->glyph->metrics.horiBearingY >> 6;
 +
                        float advance = face->glyph->advance.x >> 6;
 +
#endif
 +
                        int imgWidth = rect.Width(), imgHeight = rect.Height(), imgSize = imgWidth * imgHeight;
 +
 +
                        Pixel32* pxl = new Pixel32[imgSize];
 +
                        memset(pxl, 0, sizeof(Pixel32) * imgSize);
 +
 +
                        for (Spans::iterator s = outlineSpans.begin(); s != outlineSpans.end(); ++s) {
 +
                            for (int w = 0; w < s->width; ++w) {
 +
                                pxl[(int)((imgHeight - 1 - (s->y - rect.ymin)) * imgWidth + s->x - rect.xmin + w)] =
 +
                                    Pixel32(outlineCol.r, outlineCol.g, outlineCol.b, s->coverage);
 +
                            }
 +
                        }
 +
                        for (Spans::iterator s = spans.begin(); s != spans.end(); ++s) {
 +
                            for (int w = 0; w < s->width; ++w) {
 +
                                Pixel32& dst = pxl[(int)((imgHeight - 1 - (s->y - rect.ymin)) * imgWidth + s->x - rect.xmin + w)];
 +
                                Pixel32 src = Pixel32(fontCol.r, fontCol.g, fontCol.b, s->coverage);
 +
                                dst.r = (int)(dst.r + ((src.r - dst.r) * src.a) / 255.0f);
 +
                                dst.g = (int)(dst.g + ((src.g - dst.g) * src.a) / 255.0f);
 +
                                dst.b = (int)(dst.b + ((src.b - dst.b) * src.a) / 255.0f);
 +
                                dst.a = MIN(255, dst.a + src.a);
 +
                            }
 +
                        }
 +
                        WriteTGA(fileName, pxl, imgWidth, imgHeight);
 +
                        WriteBMP("C:/FreeTypeStep03Test.bmp", pxl, imgWidth, imgHeight);
 +
                        delete[] pxl;
 +
                    }
 +
                }
 +
            }
 +
        }
 +
    }
 +
}
 +
 +
int main(int argc, char *argv[])
 +
{
 +
    QCoreApplication a(argc, argv);
 +
 +
//    if (argc == 3) {
 +
    if (argc == 3) {
 +
        std::cerr << "Render letter `B' of given font as a TGA image.\n";
 +
        std::cerr << "\n";
 +
        std::cerr << "usage: example2 <font> TGA-file\n";
 +
        //    return 1;
 +
       
 +
    }
 +
   
 +
    QString QString_FileName = "C:/FreeTypeStep03Test.txt";
 +
    QString QString_ImageFileName = "C:/FreeTypeStep03Test.bmp";
 +
    QString filename = "C:\\Windows\\Fonts\\BIZ-UDGothicR.ttc";
 +
    QString QString_TgaFileName = "C:/FreeTypeStep03Test.tga";
 +
    QFile QFile_Text(QString_FileName);
 +
 +
    QString QString_ImageRowMonoBuffer = "";
 +
 +
    QSize QSize_xy(WIDTH, HEIGHT);
 +
    QImage QImage_Canvas(QSize_xy, QImage::Format_ARGB32_Premultiplied);//空イメージ
 +
   
 +
    FT_Library library;
 +
    FT_Init_FreeType(&library);
 +
    FT_Face face;
 +
 +
    //std::ifstream fontFile(argv[1], std::ios::binary);
 +
    std::ifstream fontFile(filename.toUtf8(), std::ios::binary);
 +
    if (fontFile) { /*フォントファイル名情報処理*/
 +
        fontFile.seekg(0, std::ios::end); //読み取り位置を 最後 に
 +
        std::fstream::pos_type fontFileSize = fontFile.tellg(); 現在位置を pos_type型 変数へ格納
 +
        fontFile.seekg(0);//読み取り位置を 0 に
 +
        unsigned char* fontBuffer = new unsigned char[fontFileSize];
 +
//読み取り位置最後はファイルサイズなので、その大きさのchar型変数配列 fontBufferを作成
 +
        fontFile.read((char*)fontBuffer, fontFileSize);
 +
//Fontファイルの内容をfontBufferへfontFileSizeの長さだけフォントファイル名取得
 +
 +
        FT_New_Memory_Face(library, fontBuffer, fontFileSize, 0, &face);
 +
//読み取ったフォントファイル名情報で&faceを初期化
 +
 +
        WriteGlyphAsTGA(library, "C:/FreeTypeStep03Test.tga"/*QString_TgaFileName.constData()*/, L'B', face, 100, Pixel32(255, 90, 30), Pixel32(255, 255, 255), 3.0f);
 +
 +
        delete[] fontBuffer;
 +
    }
 +
    return a.exec();
 +
}
 +
</Syntaxhighlight2>
 +
 +
 +
 出力結果
 +
 +
[[ファイル:FreeTypeStep03Test.jpg|||none|実行結果]]
 +
 +
 +
=== ''' サンプル4.公式サイトのErik Mollerさん作成その2 Qt版 ''' ===
 +
 これは、FreeType2.3.10以降で動作するとされているサンプルで、スタンドアロン B/W(Black/White:白黒:モノトーン)ラスタライザftraster.cの使用方法だそうです。なにが、それほど画期的なモノなのかよくわかっていませんが、フォントファイルを使わずにグラフィックを生成できます。内側と外側の概念があって塗りつぶしもできるようになっています。内側の中に外側だけの図形を描画して、穴をあけたような、図を描画できるかまでは把握していません。文字からの図形にかぎられていたのが、簡単な図形なら、描画できるようになったのが画期的なのかもしれません。
 +
 +
<Syntaxhighlight2 lang="cpp" line="1">
 +
#include <QtCore/QCoreApplication>
 +
 +
#include "ftraster.c"
 +
#include "ftraster.h"
 +
#include "ftmisc.h"
 +
#include <fstream>
 +
 +
#include <ft2build.h>
 +
#include FT_FREETYPE_H
 +
 +
#include FT_SYSTEM_H
 +
#include FT_OUTLINE_H
 +
 +
struct Vec2 {
 +
    Vec2(float a, float b) : x(a), y(b){}
 +
    float x, y;
 +
};
 +
 +
static Vec2 k_shape[] = {
 +
    Vec2(- 3, -18), Vec2(  0, -12), Vec2(  6, -10), Vec2( 12, - 6), Vec2( 12, - 4),
 +
    Vec2( 11, - 4), Vec2( 10, - 5), Vec2( 10,  1), Vec2(  9,  6), Vec2(  7,  10),
 +
    Vec2(  5,  12), Vec2(  4,  15), Vec2(  3,  14), Vec2(  1,  13), Vec2(- 1,  13),
 +
    Vec2(- 5,  11), Vec2(- 8,  8), Vec2(-11,  2), Vec2(-11, - 2), Vec2(-14,  0),
 +
    Vec2(-14, - 2), Vec2(-11, - 7), Vec2(- 9, - 9), Vec2(- 8, - 9), Vec2(- 5, -12),
 +
    Vec2(- 5, -14), Vec2(- 7, -15), Vec2(- 8, -14), Vec2(- 9, -15), Vec2(- 9, -17),
 +
    Vec2(- 7, -17), Vec2(- 6, -18)
 +
};
 +
 +
void* MY_Alloc_Func(FT_Memory memory, long size){
 +
    return malloc((size_t)size);
 +
}
 +
 +
void MY_Free_Func(FT_Memory memory, void* block) {
 +
    free(block);
 +
}
 +
 +
void* MY_Realloc_Func(FT_Memory, long cur_size, long new_size, void* block) {
 +
    return realloc(block, (size_t)new_size);
 +
}
 +
 +
static FT_Memory mem;
 +
 +
int main(int argc, char *argv[])
 +
{
 +
    QCoreApplication a(argc, argv);
 +
 +
    mem = new FT_MemoryRec_;
 +
    mem->alloc = MY_Alloc_Func;
 +
    mem->free = MY_Free_Func;
 +
    mem->realloc = MY_Realloc_Func;
 +
 +
    FT_Outline_ outline;
 +
    outline.n_contours = 1;
 +
    outline.n_points = sizeof(k_shape) / sizeof(Vec2);
 +
    outline.points = new FT_Vector[outline.n_points];
 +
 +
    for (int i = 0; i < outline.n_points; ++i) {
 +
        FT_Vector v;
 +
        v.x = (20 + k_shape[i].x) * 10 * 64;
 +
        v.y = (20 + k_shape[i].y) * 10 * 64;
 +
        outline.points[i] = v;
 +
    }
 +
    outline.tags = new char[outline.n_points];
 +
    for (int i = 0; i < outline.n_points; ++i) {
 +
        outline.tags[i] = 1;
 +
    }
 +
    outline.contours = new short[outline.n_contours];
 +
    outline.contours[0] = outline.n_points - 1;
 +
    outline.flags = 0;
 +
 +
    const int width = 500;
 +
    const int rows = 400;
 +
 +
    const int pitch_mono = (width + 7) >> 3;
 +
 +
    FT_Bitmap bmp;
 +
    FT_Raster_Params params;
 +
 +
    const int kRenderPoolSize = 1024 * 1024;
 +
    unsigned char* renderPool = new unsigned char[kRenderPoolSize];
 +
 +
    bmp.buffer = new unsigned char[rows * pitch_mono];
 +
    memset(bmp.buffer, 0, rows * pitch_mono);
 +
    bmp.width = width;
 +
    bmp.rows = rows;
 +
    bmp.pitch = pitch_mono;
 +
    bmp.pixel_mode = FT_PIXEL_MODE_MONO;
 +
 +
    memset(&params, 0, sizeof(params));
 +
    params.source = &outline;
 +
    params.target = &bmp;
 +
 +
    FT_Raster raster;
 +
 +
    ft_standard_raster.raster_new(mem, &raster);
 +
    ft_standard_raster.raster_reset(raster, renderPool, kRenderPoolSize);
 +
    ft_standard_raster.raster_render(raster, &params);
 +
 +
    std::ofstream out_mono("out-mono.pbm", std::ios::binary);
 +
    out_mono << "P4 " << width << " " << rows << "\n";
 +
    out_mono.write((const char*)bmp.buffer, rows * pitch_mono);
 +
 +
    delete[] renderPool;
 +
    delete[] bmp.buffer;
 +
    delete[] outline.points;
 +
    delete[] outline.tags;
 +
    delete[] outline.contours;
 +
    delete mem;
 +
 +
    return a.exec();
 +
}
 +
 +
</Syntaxhighlight2>
 +
 +
 +
 実行結果
 +
 +
[[ファイル:Out-mono.bmp|||none|実行結果]]
 +
 +
 +
 上記のようなプログラムで更に、いろいろと必要なプログラムをつまびかないと動作させられませんでした。
 +
 +
 プロジェクトにftimege.hとftmisc.hとfutil.cをライブラリディレクトリからコピーしてmain.cppと同じフォルダに追加。
 +
 +
 プロジェクトのフォルダの中にftraster.cをコピーする必要があり、このファイルはインクルードすることでコンパリンの対象になります。そして、以下のインクルードをftraster.cの中に記載。
 +
 +
#include "C:\xxx\xxx\xxx\xxx\freetype-2.9.1\include\freetype\internal\internal.h"
 +
#include "C:\xxx\xxx\xxx\xxx\freetype-2.9.1\include\freetype\internal\ftdebug.h"
 +
#include "C:\xxx\xxx\xxx\xxx\freetype-2.9.1\include\freetype\internal\ftcalc.h"
 +
 +
 編集するファイルはすべて、プロジェクト側のディレクトリに移動します。本格的にB/Wラスタライズ機能を繰り返し使う場合は、もっと構成を簡単に綺麗にした方がよいでしょう。自分は無理やりでも動かしてやるということに集中しました。正直、なんにも文献もないのに、全貌を読み解くのは面倒です。簡単には使えないということを理解しました。美しい使い方については、自分もスタンドアロンB/Wラスタライズを使うときに考えたいと思います。
 +
 +
 FT_THROWは存在しない構文としてエラーになって面倒だったので、消しました。例外エラーがでたときにわかりやすくなる命令ではありますが、なくても問題ない。return FT_THROW(... となっている場合はreturn 1;に置き換え。if文の中に1行だけある場合は、int ii = 1; のような疑似プログラムを追加。
 +
 +
 取り込んだftmisc.hも
 +
 +
<Syntaxhighlight2 lang="cpp" line="1">
 +
  /*
 +
  typedef struct FT_MemoryRec_
 +
  {
 +
    void*            user;
 +
 +
    FT_Alloc_Func    alloc;
 +
    FT_Free_Func    free;
 +
    FT_Realloc_Func  realloc;
 +
 +
  } FT_MemoryRec;
 +
  */
 +
</Syntaxhighlight2>
 +
 +
 の部分をコメントアウトが必要になります。重複する部分になります。
 +
 +
 ftobjs.hも
 +
 +
<Syntaxhighlight2 lang="cpp" line="1">
 +
#define FT_INTERNAL_MEMORY_H              <freetype/internal/ftmemory.h>
 +
#include FT_INTERNAL_MEMORY_H
 +
#define FT_INTERNAL_GLYPH_LOADER_H        <freetype/internal/ftgloadr.h>
 +
#include FT_INTERNAL_GLYPH_LOADER_H
 +
#define FT_INTERNAL_DRIVER_H              <freetype/internal/ftdrv.h>
 +
#include FT_INTERNAL_DRIVER_H
 +
#define FT_INTERNAL_AUTOHINT_H            <freetype/internal/autohint.h>
 +
#include FT_INTERNAL_AUTOHINT_H
 +
#define FT_INTERNAL_SERVICE_H            <freetype/internal/ftserv.h>
 +
#include FT_INTERNAL_SERVICE_H
 +
#define FT_INTERNAL_PIC_H                <freetype/internal/ftpic.h>
 +
#include FT_INTERNAL_PIC_H
 +
#define FT_INTERNAL_CALC_H                <freetype/internal/ftcalc.h>
 +
#include FT_INTERNAL_CALC_H
 +
</Syntaxhighlight2>
 +
 +
 #defineの行を追加しなければならないところが発生しました。
 +
 +
 +
 ftutil.hも
 +
 +
<Syntaxhighlight2 lang="cpp" line="1">
 +
#include "C:\xxx\xxx\xxx\xxx\freetype-2.9.1\include\freetype\internal\ftdebug.h"
 +
#include "C:\xxx\xxx\xxx\xxx\freetype-2.9.1\include\freetype\internal\ftmemory.h"
 +
#include ".\ftobjs.h"
 +
</Syntaxhighlight2>
 +
 +
 のように編集が必要なヘッダファイルはプロジェクト内のものを相対パス参照で、編集が不要でインクルードが必要なモノはライブラリのものを読み込みます。絶対パスでxxxになっているところは、各自の環境に合わせてください。
 +
 +
 +
 これで編集が必要だった箇所を全部を書いたかはわかりませんが、基本的にはやることは同じです。コンパリンエラーになった結果にもとづいて、定義が見つからない場合は、必要なインクルードファイルを追記。リンクエラーになる場合は宣言しか読み込めていないってことなので、プログラムファイルを探して読み込みという感じの作業をします。ファイルの編集が必要な場合はライブラリのものは触らず、ファイルを自分のプロジェクトにコピーしてきて、それを使うように読み込みする仕組みにしたうえで編集をします。
 +
 +
 +
 こういう作業をすることは勉強にはなりません。これを当たり前のようにこなしてから、どうすることが正しかったのかを考えることが大事です。でも今回は管理人には必要ない知識なので、ここではやりません。人が作ったものを使いこなすのは大変だということです。ユーザ数が少ない。文献がすくなければなおさら面倒です。必要に駆られるまで手は出さない。動けばいいな。程度。1週間やってもだめなら諦めますね。パズルと同じです。
 +
 +
 +
=== ''' サンプル5.公式サイトのRóbert Márkiさん作成 ''' ===
 +
 Róbertが作成したQtプロジェクトでのサンプルですが、プロジェクトファイルが配布されているわけでもないので、再現するのにはどうすればいいのかが、よくわからなかったので、自分でイチカラQt Guiプロジェクト作りこみながら、再現してみました。qmakeファイル配布されてましたけど、何をどうしろというのかというような内容でして、ここまで来て思ったんですけど、FreeTypeのサンプル集はどれも冷たい。どうやってビルドするべきかを環境とツールごとに分けて記述しているプロジェクトもある昨今なのに、ソースファイルとよくわからない*.proファイルの配布。冷たい。
 +
 +
 +
 不満を並べてもしょうがないので、ビルド手順を簡単に。
 +
 +
 1.VisualStudioでQt Wigets Applicationの新規作成を始めます。
 +
 +
 2.今回の場合Wizardがいろいろと分類してくれるのは、ありがたくないのですが、Wizardのいうとおりに作るので、BaseClassを指定するところだけQMainWindowではなく、QWidgetに変更します。あとは規定値でよいです。
 +
 +
 3.したらばプログラムを書きます。分割プログラムの形式なのでやや複雑ですが、以下の通り記述します。
 +
 +
 まずmain.cpp
 +
 +
<Syntaxhighlight2 lang="cpp" line start="1">
 +
#include "QtSampleProject.h"
 +
#include <QtWidgets/QApplication>
 +
 +
#include <QPainter>
 +
#include <QFile>
 +
 +
#include <iostream>
 +
 +
#include <ft2build.h>
 +
#include FT_FREETYPE_H
 +
#include FT_OUTLINE_H
 +
 +
QString g_usageText =
 +
"usage:\n"
 +
"example4 FONT_PATH CHARACTER SIZE DIRECT_RENDERING_MODE(1|0)";
 +
 +
int main(int argc, char *argv[])
 +
{
 +
    bool status = false;
 +
    bool isSizeOk = false;
 +
    QString path = "C:\\Windows\\Fonts\\BIZ-UDGothicR.ttc";
 +
    QChar character = 'm';
 +
    int size = QString("60").toInt(&isSizeOk);
 +
    bool directRender = QString("1").toInt();
 +
 +
    if (QFile::exists(path) && isSizeOk) {
 +
        status = true;
 +
        QApplication a(argc, argv);
 +
        QtSampleProject w(path, character, size, directRender);
 +
        w.show();
 +
        return a.exec();
 +
    }
 +
    if (!status) {
 +
        std::cout << qPrintable(g_usageText) << std::endl;
 +
        return 0;
 +
    }
 +
}
 +
 +
 +
</Syntaxhighlight2>
 +
 +
 +
プロジェクト名.cpp ここでは仮にQtSampleProjectとしておきます。
 +
 +
<Syntaxhighlight2 lang="cpp" line start="1">
 +
#include "QtSampleProject.h"
 +
 +
QtSampleProject::QtSampleProject(QWidget *parent)
 +
    : QWidget(parent)
 +
{
 +
    m_face = 0;
 +
    m_library = 0;
 +
    m_directRender = 0;
 +
    ui.setupUi(this);
 +
}
 +
QtSampleProject::QtSampleProject(const QString& fileName, QChar character, int pointSize, bool directRender, QWidget* parent) : QWidget(parent), m_directRender(directRender)
 +
{
 +
    FT_Error error = FT_Err_Ok;
 +
    m_face = 0;
 +
    m_library = 0;
 +
    error = FT_Init_FreeType(&m_library);
 +
    if(!error){
 +
        error = FT_New_Face(m_library, fileName.toLatin1().constData(), 0, &m_face);
 +
 +
        if (!error) {
 +
            error = FT_Set_Char_Size(m_face, 0, pointSize * 64, physicalDpiX(), physicalDpiY());
 +
            if (!error) {
 +
                FT_UInt glyph_index = 0;
 +
                glyph_index = FT_Get_Char_Index(m_face, character.unicode());
 +
                error = FT_Load_Glyph(m_face, glyph_index, FT_LOAD_DEFAULT);
 +
                if (!error) {
 +
                    FT_Pos left = m_face->glyph->metrics.horiBearingX;
 +
                    FT_Pos right = left + m_face->glyph->metrics.width;
 +
                    FT_Pos top = m_face->glyph->metrics.horiBearingY;
 +
                    FT_Pos bottom = top - m_face->glyph->metrics.height;
 +
 +
                    m_glyphRect = QRect(QPoint(TRUNC(left),
 +
                        -TRUNC(top) + 1),
 +
                        QSize (TRUNC(right - left) + 1,
 +
                            TRUNC(top - bottom) + 1)
 +
                    );
 +
                    setFixedSize(m_glyphRect.width(), m_glyphRect.height());
 +
                }
 +
            }
 +
        }
 +
    }
 +
    ui.setupUi(this);
 +
}
 +
QtSampleProject::~QtSampleProject()
 +
{
 +
    if(m_face){
 +
        FT_Done_Face(m_face);
 +
    }
 +
    if(m_library){
 +
        FT_Done_FreeType(m_library);
 +
    }
 +
   
 +
}
 +
 +
void QtSampleProject::graySpans(int y, int count, const FT_Span_* spans, void* user) {
 +
    QPainter* painter = (QPainter*)user;
 +
    y = -y;
 +
 +
    for (int i = 0; i < count; i++) {
 +
        const FT_Span span = spans[i];
 +
        qreal opacity = qreal(span.coverage) / 255.0;
 +
 +
        painter->setOpacity(opacity);
 +
 +
        if (span.len > 1) {
 +
            painter->drawLine(span.x, y, span.x + span.len - 1, y);
 +
        }
 +
        else {
 +
            painter->drawPoint(span.x, y);
 +
        }
 +
    }
 +
}
 +
 +
void QtSampleProject::paintEvent(QPaintEvent* event) {
 +
    QWidget::paintEvent(event);
 +
 +
    if (m_library && m_face) {
 +
        FT_Error error = FT_Err_Ok;
 +
        QPainter painter(this);
 +
        painter.translate(-m_glyphRect.x(), -m_glyphRect.y());
 +
        if (m_directRender) {
 +
            painter.setPen(Qt::black);
 +
            FT_Raster_Params params;
 +
 +
            params.target = 0;
 +
            params.flags = FT_RASTER_FLAG_DIRECT | FT_RASTER_FLAG_AA;
 +
            params.user = &painter;
 +
            params.gray_spans = &QtSampleProject::graySpans;
 +
            params.black_spans = 0;
 +
            params.bit_set = 0;
 +
            params.bit_test = 0;
 +
 +
            FT_Outline* outline = &m_face->glyph->outline;
 +
            FT_Outline_Render(m_library, outline, &params);
 +
        }
 +
        else {
 +
            error = FT_Render_Glyph(m_face->glyph, FT_RENDER_MODE_NORMAL);
 +
            QImage glyphImage(m_face->glyph->bitmap.buffer,
 +
                m_face->glyph->bitmap.width,
 +
                m_face->glyph->bitmap.rows,
 +
                m_face->glyph->bitmap.pitch,
 +
                QImage::Format_Indexed8);
 +
            painter.translate(m_glyphRect.x(), m_glyphRect.y());
 +
 +
            QVector<QRgb> colorTable;
 +
            for (int i = 0; i < 256; ++i) {
 +
                colorTable << qRgba(0, 0, 0, i);
 +
            }
 +
            glyphImage.setColorTable(colorTable);
 +
            painter.drawImage(QPoint(0, 0), glyphImage);
 +
        }
 +
    }
 +
}
 +
</Syntaxhighlight2>
 +
 +
プロジェクト名.h
 +
 +
<Syntaxhighlight2 lang="cpp" line start="1">
 +
#pragma once
 +
 +
#include <QtWidgets/QWidget>
 +
#include "ui_QtSampleProject.h"
 +
 +
#include <QPainter>
 +
#include <QFile>
 +
 +
#include <iostream>
 +
 +
#include <ft2build.h>
 +
#include FT_FREETYPE_H
 +
#include FT_OUTLINE_H
 +
 +
#define TRUNC(x) ((x) >> 6)
 +
 +
class QtSampleProject : public QWidget
 +
{
 +
    Q_OBJECT
 +
 +
public:
 +
    QtSampleProject(QWidget* parent = nullptr);
 +
    QtSampleProject(const QString& fileName, QChar character, int pointSize, bool directRender, QWidget* parent = 0);
 +
  ~QtSampleProject();
 +
 +
private:
 +
    bool m_directRender = 0;
 +
    FT_Library m_library = 0;
 +
    FT_Face m_face = 0;
 +
    QRect m_glyphRect;
 +
 +
    static void graySpans(int y, int count, const FT_Span_* spans, void* user);
 +
    Ui::QtSampleProjectClass ui;
 +
 +
protected:
 +
    void paintEvent(QPaintEvent* event);
 +
};
 +
 +
</Syntaxhighlight2>
 +
 +
 実行結果
 +
 +
[[ファイル:QtConsoleFreeTypeStep07.png|||none|実行結果]]
 +
 +
 +
 ウィンドウっちっさ!サンプルの通り動かしたらコレなので、自信を持ってお届けする結果です。main.cppの中のmain関数の最初の方に利用するフォント名を設定する箇所、レンダリングに使う文字、文字の大きさ、directRenderとそうじゃない描画のどちらを使うかを決める0 or 1を指定する文字列で結果が、もう少し変わります。このサンプルをもうちょっと面白くするならこの手前でやったようなサンプルのように文字列を与える形式にするとウィンドウがもう少し大きくなって楽しいかもしれません。あるいは変形とかね。
 +
 +
 +
=== ''' サンプル6.公式サイトのStatic Jobs LLC作成 ''' ===
 +
  Static Jobs LLCという組織によって作られたサンプルがありますが、こちらは、ほぼ変更する必要もなく出力結果のSVGのXMLが標準出力に描画されます。単位はemですが、htmlに単純に貼りつけるとpixelとして扱われます。ものすごく大きい描画になりますが、具体的に活用するときになるまでに、これを編集すればいいことなので、大きな問題ではないです。
 +
 +
 +
 このプログラムのSVG Printで拡大率を縮小する方向にするため ビューポート情報を追加すると簡単に縮小された結果を得ることができると思います。そうやってUD BIZゴシックについてグリフQを書き出したも結果は以下のようになります。
 +
 +
<Syntaxhighlight2 lang="xml">
 +
<svg xmlns='http://www.w3.org/2000/svg'
 +
    xmlns:xlink='http://www.w3.org/1999/xlink'
 +
    width='9px' height='17px' viewBox='63 -1640 922 1796'>
 +
  <path d='
 +
          M 901 156
 +
          Q 812 61, 745 -49
 +
          Q 647 84, 502 84
 +
          Q 303 84, 183 -152
 +
          Q 63 -389, 63 -783
 +
          Q 63 -1179, 179 -1408
 +
          Q 295 -1640, 502 -1640
 +
          Q 704 -1640, 824 -1407
 +
          Q 946 -1171, 946 -783
 +
          Q 946 -414, 839 -166
 +
          Q 890 -73, 985 12
 +
          L 901 156
 +
          M 501 -563
 +
          Q 609 -448, 718 -295
 +
          Q 772 -456, 772 -772
 +
          Q 772 -1109, 699 -1299
 +
          Q 627 -1486, 504 -1486
 +
          Q 393 -1486, 322 -1330
 +
          Q 235 -1138, 235 -781
 +
          Q 235 -445, 311 -253
 +
          Q 385 -66, 507 -66
 +
          Q 597 -66, 647 -162
 +
          Q 540 -315, 423 -438
 +
          L 501 -563
 +
          '
 +
        fill='red'/>
 +
</svg>
 +
</Syntaxhighlight2>
 +
 +
 +
 このようにフォントの描画を行うためのベジェ曲線の通過座標とその補助座標といった情報に分解されたような情報を得ることができます。Mではじまるのは、そのあとに書かれた座標への移動。Qはそのあとに書かれた2つ座標について、現在の始点は前回の移動点であるとして、1つの制御点と1つの終点をもっています。始点と終点を結んだ直線に対して、制御点が曲線の膨らみや凹みを表します。こういうものを2次ベジェと呼んでいます。3次ベジェはCで始まります。Lは、前回の移動点を始点として、1つの指定された座標を終点とする直線を描画します。詳しくはSVGについての記事を読む必要があると思います。フォント情報はほぼ、このM,L,Q,Cで構成されています。
 +
 +
 +
 +
 これまで見てきたようにFreeTypeはオープンソースでありながらフォント情報から、グラフィックに描画するための情報を抜き出して、かつ、ビットマップにレンダリングする機能。単独で図形を描画する機能。フォントファイルに埋め込まれたベジェ曲線を描くのに必要な情報の抜き出しが出来ました。
 +
 +
 +
 文字コードに対応するグリフ番号を取得する命令も保有しています。フォント情報を編集することはできませんが、もうひとつのフォントファイルを再構築するような作業もやれなくはない感じがします。プログラムによってテキストや独自の形式で使われた文字コードを蓄積し、そのすべてのグリフ番号を取得することができます。その使われたフォント情報だけを抜き出して再構築するような作業もやれそうですが、freetypeだけでやれるのか。もう少し見ていく必要はありそうです。
 +
 +
 +
 freetypeは文献が少ないですが、普段、われわれが使ってるようなソフトウェアでも使われていたりと実績はかなりあるようです。FreeTypeを使ってPDFに必要な文字サブセット作成とグリフ番号取得への道のりを考えていましたが、もっと発展的に使えばフォントファイルをプログラムによってすべてを一定の手法によって改造した文字を作ることも可能です。角の処理だけを変えたりですね。大量の文字をプログラムによって編集したものを新たなフォントにできるのであれば、手軽でありながら画期的です。
 +
 +
  
 
[[フォント TrueType 構造解析]]に戻る。
 
[[フォント TrueType 構造解析]]に戻る。

2022年8月23日 (火) 00:00時点における最新版



個人用ツール
名前空間

変種
操作
案内
ツールボックス