FreeTypeを使う 新しいページはコチラ
提供: yonewiki
(→サンプル1.Qt ConsoleApplicationでテキスト画像表示の解説) |
(→サンプル6.公式サイトのStatic Jobs LLC作成) |
||
233行: | 233行: | ||
FT_Vector pen; | FT_Vector pen; | ||
pen.x = LEFT64(LEFT); | pen.x = LEFT64(LEFT); | ||
− | pen.y = | + | pen.y = LEFT64(TOP); |
unsigned int idces[LEN]; | unsigned int idces[LEN]; | ||
for (int i = 0; i < LEN; i++) { | for (int i = 0; i < LEN; i++) { | ||
319行: | 319行: | ||
FreeTypeの勉強っていうけど、一般の人からしたら、だから何がうれしいのっていうようなプログラムですね。日々、目にしているグラフィックツールのテキストレンダリングは、これぐらい膨大なプログラミングによって実現されているっていうね。テレビや動画や音楽もしかり、実はものすごい技術です。LEDで文字が表示されてる看板とかね。この形の文字になるように、だれかが作ってんだよって言ってやりたいね。手で書いてるのとは訳が違う。そういえば、コンピュータのごとく手で書いちゃう人もいましたね。あれはあれであれのほうが凄い。コンピュータは誰かが苦労して書いたやつをみんなで再利用してるという効率の良さ。原理を知らない人ばかりになるとどうなるんだこの世界。 | FreeTypeの勉強っていうけど、一般の人からしたら、だから何がうれしいのっていうようなプログラムですね。日々、目にしているグラフィックツールのテキストレンダリングは、これぐらい膨大なプログラミングによって実現されているっていうね。テレビや動画や音楽もしかり、実はものすごい技術です。LEDで文字が表示されてる看板とかね。この形の文字になるように、だれかが作ってんだよって言ってやりたいね。手で書いてるのとは訳が違う。そういえば、コンピュータのごとく手で書いちゃう人もいましたね。あれはあれであれのほうが凄い。コンピュータは誰かが苦労して書いたやつをみんなで再利用してるという効率の良さ。原理を知らない人ばかりになるとどうなるんだこの世界。 | ||
− | |||
− | |||
=== '''サンプル1.Qt ConsoleApplicationでテキスト画像表示の解説 ''' === | === '''サンプル1.Qt ConsoleApplicationでテキスト画像表示の解説 ''' === | ||
363行: | 361行: | ||
+ | 60行目からはメイン処理の追加文です。 | ||
− | |||
+ | QtのQStringで画像ファイルとテキストファイルのパスを指定します。長いパスは疲れるのでC:¥とCドライブの直下に置くことにしました。 | ||
+ | |||
+ | |||
+ | テキストクラスのQFileに初期値として、パス名を与えることで、初期化できるので利用した感じです。あとから違うファイルを開くためにファイルをcloseした状態でならファイルパスを変更することもできると思いますが、今回はやっていません。 | ||
+ | |||
+ | |||
+ | QString_ImageRowMonoBufferというQStringクラスの変数を実体化していますが、これは後々、キャンバスサイズの1行分の白黒情報を2桁の整数で吐き出します。実際は0と1の2値なので、1文字空白が発生するようになります。テキストでみたときに2文字が全角1文字なので、1文字が正方形で表せるため縦横比がテキストファイル上でいい感じにとらえることができます。 | ||
+ | |||
+ | |||
+ | 67行目でQImageクラスの変数を実体化しています。空の画像格納用変数になります。最初の引数に画像の横幅と縦幅を格納したQSizeクラスの値が必要なので、その上の行で、QSize クラスの変数を実体化しています。第二引数はどういう形式の画像にするかというフラグ設定列挙子です。今回はフルカラーRGBと透明度が設定可能な形式。 | ||
+ | |||
+ | |||
+ | 69行目からはfreetypeのおきまりの初期化が始まります。91行目までは、このようなサンプルのおきまりの初期化の流れになります。freetypeの初期化をlibraryというクラス変数で実施して、次にその初期化されたクラス変数設定状態を有した状態でフォント情報を保持したfaceというクラスを生成。 | ||
+ | |||
+ | |||
+ | 81行目 | ||
+ | |||
+ | FT_New_Face(library, qPrintable("C:\\Windows\\Fonts\\KozGoPr6N-Medium.otf"), 0, &face); | ||
+ | |||
+ | |||
+ | のようにして、face変数に第一引数のフォント名を関連付けたオブジェクトとして操作できるようになる。 | ||
+ | |||
+ | |||
+ | そして87行目で1文字を読み取るためのグリフサイズをポイント値で指定。第一引数はこれまでの初期化で生成されたオブジェクトfaceで、次の2つ引数は横幅、縦幅の大きさをポイント*64倍の値を設定する。ここで8は8ポイントを意味していて、ここに指定する1単位は1/64倍されたポイント数になるので、8ポイントにするための8/64を64倍して、8ポイントとして扱うことができる。このとき、このポイント数は26.6<span>(</span>ニィロク ロク<span>)</span>固定小数と呼ぶ形式になっていると言える。どちらかに値が設定されていればいい。片方は0なら、フォント自身が持つ縦横比が使われる。両方をしていした場合は縦横比が変形する。その後ろの2つつづく300は横・縦の内部解像度の扱いを指定している。こちらも片方が0なら、どちらかの値と同じになる。表示する部分がせいぜい8ポイント程度の画面上の表示でも1インチあたりは300個の情報を内部的に保持する形式になり、印刷してもそれなりに綺麗にみえる。300dpiというが、これは印刷業界の一般的な知識です。300dpiなら、人間にはそれなりに見えるということだ。画面上で拡大していくと汚くても。印刷したら大丈夫。これが大事。 | ||
+ | |||
+ | |||
+ | ここまでの処理には、不具合があるとError番号に0以外が渡されるので、それを元にエラーがあって強制終了したことを標準エラー出力画面に表示させるStdErrでプログラマに知らせる形式となっている。 | ||
+ | |||
+ | |||
+ | 93・94行目は26.6形式に対応するべく、64倍するという操作をマクロ化しています。何回か使うのでスッキリ表示したいというものです。 << とか >> は演算子のオーバーロードが働いているC++のストリーム標準出力とはちがい、オーバーロード宣言される前の標準演算子で言えば、論理シフトという操作になります。 << は左論理シフトです。 >>は右論理シフト。 論理シフトを行うと 2進数の 0b0010が左だと0b0100のように左にひとつ移動し、右からは0が挿入されます。このとき10進数で言えば、2から4に増えています。実は1つ移動するごとに2倍されいます。つまり6回移動すると64倍です。2, 4 , 8, 16, 32, 64 ね。逆に右へ移動させると、1/2倍されていきます。 | ||
+ | |||
+ | |||
+ | しらないと、何やってんのコレって思っちゃいますが、わかるとなんてことはない感じです。 | ||
+ | |||
+ | |||
+ | 97・98行目では算出した初期値へ移動するため、FT_Vector型の座標を記憶できる構造のクラスのメンバ変数 pen.x, pen.y にそれぞれ、LEFT 16とTOP 32のポイント値を64倍した値を記憶させています。 | ||
+ | |||
+ | |||
+ | そして次の行で、テキストの文字数にあたる大きさの配列を準備しています。あとで、それぞれのテキスト番号に対応するグリフ番号を取得する予定です。 | ||
+ | |||
+ | |||
+ | そして100行目から126行目の間を、テキストの文字数分だけ繰り返します。このとき繰り返しごとに増えていく数値が操作する入力テキスト文字コード配列の番号と対応します。 | ||
+ | |||
+ | |||
+ | まず101行目のif文ではテキスト文字が改行コードかどうかを判断します。該当したら、pen.y への移動量を設定して、キャンバス上の縦への移動を行います。pen.xは復帰の扱いの処理で、初期値のLEFT 16 のマクロ値を設定します。このときも、64倍した値です。yは48ピクセル下へ移動します。6文字分くらい下ですね。300dpiでは8ポイントが32ピクセル相当です。 | ||
+ | |||
+ | |||
+ | 改行コードの場合はこのif文の中の処理がおわったら、ほかの処理はもう必要ないので、全部を無視して、for文の最初にもどり、繰り返し処理を再開します。 | ||
+ | |||
+ | |||
+ | 107行目は、文字コードを含んだ text 配列の for文処理の対象文字について、フォントファイルのCMapと照合して、グリフ番号を返してくれる命令です。 | ||
+ | |||
+ | |||
+ | <Syntaxhighlight2 lang="cpp" line start="107"> | ||
+ | idces[i] = FT_Get_Char_Index(face, text[i]); | ||
+ | </Syntaxhighlight2> | ||
+ | |||
+ | |||
+ | 引数にはフォント情報をもっている face を第一引数として設定、第二引数で文字コード番号を含んだ数値を設定。これで戻り値にはグリフ番号が取得でき、結果として、for文の処理が終わった所で文字コードをグリフ番号に変換することを実現している。この部分は、PDFで使った文字コードをグリフ番号で保持するPDFの仕様に対応できる部分だ。 | ||
+ | |||
+ | |||
+ | 次の行ではフォントの情報を保持できるクラスに対して、対象のグリフ番号の情報を保持するという処理を行う。まずは、情報を読み込む処理として | ||
+ | |||
+ | |||
+ | <Syntaxhighlight2 lang="cpp" line start="109"> | ||
+ | error = FT_Load_Glyph(face, idces[i], FT_LOAD_DEFAULT); | ||
+ | </Syntaxhighlight2> | ||
+ | |||
+ | |||
+ | 109行目のFT_Load_Glyphを実行する。第一引数は一文字分のグリフ情報を保持するためのFT_Face クラスの変数 face を使う。先のグリフ番号取得でも使った face の役割はいくつかありそう。そうして読み込んだ状態では、そのメンバ関数がグリフに関する情報をさらに保持していけるので、次の命令を実行できる。 | ||
+ | |||
+ | |||
+ | それが115行目だ。 | ||
+ | |||
+ | <Syntaxhighlight2 lang="cpp" line start="115"> | ||
+ | error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); | ||
+ | </Syntaxhighlight2> | ||
+ | |||
+ | |||
+ | FT_Render_Glyphの第一引数の face->glyph メンバポインタ変数に描画後の情報の標準的なbitmap値を生成する。FT_RENDER_MODE_NORMALという列挙子によってbitmapに生成する仕組みがかわるようだ。これで、使えそうなbitmapが生成されたので、次の命令によって、キャンバスに反映させる。ここではキャンバスに反映させるが、ここはプログラムの用途によっていろいろな方法で情報を受け取ることになると思う。 | ||
+ | |||
+ | |||
+ | このチュートリアルでは逐一、テキスト配列を処理していく毎にキャンバスの色情報保持配列に反映させてしまう。それが121行目から125行目の処理となる。 | ||
+ | |||
+ | |||
+ | ビットマップ値はFT_Face型の変数faceを使って &face->glyph->bitmapのアドレス番号を先頭に保持されているので、この情報をプログラマ側で作ったキャンバスへ反映するdrawという関数の第一引数に指定する。この関数は、描画位置情報のxとyの値も必要としているので、改行したり、文字を描画がしたときに次のpenの位置を移動したりする操作をしていく。 | ||
+ | |||
+ | |||
+ | draw関数では、face->glyph->bitmap_leftに読み込んだビットマップの左側の座標を渡す。topは上側だ。かならずしも端の座標は 0 とは限らない。フォントをはみ出してマイナスの位置からグリフ情報をもっている可能性もあるためだ、その場合、少し戻った位置にたいしてキャンバスへの描画をおこなう必要がある。 | ||
+ | |||
+ | |||
+ | 文字の送り幅自体はface->glyph->advance.xでpen.xに反映させる。125行目 | ||
+ | |||
+ | |||
+ | 字詰めという処理を必要とするプロポーショナルフォントの場合は特に上記のような操作にしておかないと、はみ出た分の文字のデザインが描画されないばかりか、バランスがおかしくなる。 | ||
+ | |||
+ | |||
+ | これがおわるとキャンバスの配列ができあがる。これをビットマップに吐き出したり、標準出力に吐き出したり、テキストとして保存してみたりというのが、この先の処理だ。結果を目にみえるものにするのは楽しい。やってることは、単純だが、ここまで苦労して得られたものは、プログラマにしかわからない優越感を味わえる。フォントをイチカラ活用してやったぜ。っていう感じです。 | ||
+ | |||
+ | |||
+ | 129~134行目であらかじめ準備していたQImageの空のイメージ空間を使って、キャンバスの全ピクセル値を使って白黒のグレースケールイメージファイルを構成し、保存する処理を実施している。 | ||
+ | |||
+ | |||
+ | 137行目から最後まではテキストファイルの生成処理を実施している。このあたりはfreetypeとはもう関係ないので説明しない。 | ||
+ | |||
+ | === '''サンプル2. 公式サイト版 のQtConsolApp''' === | ||
+ | <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 | ||
+ | |||
+ | #define WIDTH 640 | ||
+ | #define HEIGHT 480 | ||
+ | #define LEFT 32 | ||
+ | #define TOP 32 | ||
+ | |||
+ | static unsigned char canvas[HEIGHT][WIDTH]; | ||
+ | |||
+ | static void draw_bitmap(FT_Bitmap* bitmap, FT_Int x, FT_Int y) { | ||
+ | FT_Int i, j, p, q; | ||
+ | FT_Int x_max = x + bitmap->width; | ||
+ | FT_Int y_max = y + bitmap->rows; | ||
+ | |||
+ | for (i = x, p = 0; i < x_max; i++, p++) { | ||
+ | for (j = y, q = 0; j < y_max; j++, q++) { | ||
+ | if (i < 0 || j < 0 || i >= WIDTH || j >= HEIGHT) { | ||
+ | continue; | ||
+ | } | ||
+ | |||
+ | canvas[j][i] |= bitmap->buffer[q * bitmap->width + p]; | ||
+ | |||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | int main(int argc, char* argv[]) | ||
+ | { | ||
+ | QCoreApplication a(argc, argv); | ||
+ | |||
+ | QString QString_FileName = "C:/FreeTypeStep02Test.txt"; | ||
+ | QString QString_ImageFileName = "C:/FreeTypeStep02Test.bmp"; | ||
+ | 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_Face face; | ||
+ | |||
+ | |||
+ | FT_GlyphSlot slot; | ||
+ | FT_Matrix matrix; | ||
+ | FT_Vector pen; | ||
+ | FT_Error error; | ||
+ | |||
+ | QString text = QString::fromUtf8(u8"あいさがしの果てに\n辿り付いた寝屋川市駅\nというtest文章"); | ||
+ | |||
+ | // ヒラギノ角5 DShirkg5.ttc BIZ UD ゴシック BIZ-UDGothicR.ttc KozGoPr6N-Medium.otf | ||
+ | QString filename = "C:\\Windows\\Fonts\\BIZ-UDGothicR.ttc"; | ||
+ | QString SingleStr = ""; | ||
+ | |||
+ | |||
+ | double angle; | ||
+ | int target_height; | ||
+ | int n; | ||
+ | int num_chars; | ||
+ | |||
+ | |||
+ | |||
+ | num_chars = text.length(); | ||
+ | angle = (0.0 / 360.0) * 3.141592 * 2.0; | ||
+ | target_height = HEIGHT; | ||
+ | |||
+ | error = FT_Init_FreeType(&library); | ||
+ | |||
+ | if (error) { | ||
+ | fprintf(stderr, qPrintable("ft init error\n")); | ||
+ | exit(1); | ||
+ | } | ||
+ | |||
+ | error = FT_New_Face(library, filename.toUtf8(), 0, &face); | ||
+ | if (error) { | ||
+ | fprintf(stderr, qPrintable("new face error\n")); | ||
+ | exit(1); | ||
+ | } | ||
+ | |||
+ | error = FT_Set_Char_Size(face, 30 * 64, 0, 100, 0); | ||
+ | if (error) { | ||
+ | fprintf(stderr, qPrintable("set size error\n")); | ||
+ | exit(1); | ||
+ | } | ||
+ | |||
+ | slot = face->glyph; | ||
+ | |||
+ | #define LEFT64(x) ((x) << 6) // 1/64が1の 32 bit 26.6 ニィロク ロク固定小数。 | ||
+ | #define RIGHT64(x) ((x) >> 6) | ||
+ | |||
+ | |||
+ | pen.x = LEFT64(LEFT); | ||
+ | pen.y = LEFT64(target_height - 200); | ||
+ | |||
+ | matrix.xx = (FT_Fixed)(cos(angle) * 0x10000L); | ||
+ | matrix.xy = (FT_Fixed)(-sin(angle) * 0x10000L); | ||
+ | matrix.yx = (FT_Fixed)(sin(angle) * 0x10000L); | ||
+ | matrix.yy = (FT_Fixed)(cos(angle) * 0x10000L); | ||
+ | |||
+ | unsigned int* idces = new unsigned int[num_chars]; | ||
+ | for (int i = 0; i < num_chars; i++) { | ||
+ | if (text.at(i) == '\n') { | ||
+ | fprintf(stderr, qPrintable("%ld\n"), pen.x); | ||
+ | pen.x = LEFT64(LEFT); | ||
+ | pen.y += LEFT64(48); | ||
+ | continue; | ||
+ | } | ||
+ | |||
+ | SingleStr = text.at(i); | ||
+ | |||
+ | FT_Set_Transform(face, &matrix, &pen); | ||
+ | |||
+ | QChar ch = SingleStr.at(0); | ||
+ | FT_ULong FT_ULong_code = ch.unicode(); | ||
+ | idces[i] = FT_Get_Char_Index(face, FT_ULong_code); | ||
+ | |||
+ | error = FT_Load_Char(face, FT_ULong_code, FT_LOAD_RENDER); | ||
+ | if (error) { | ||
+ | continue; | ||
+ | } | ||
+ | draw_bitmap(&slot->bitmap, slot->bitmap_left, HEIGHT - slot->bitmap_top); | ||
+ | |||
+ | pen.x += slot->advance.x; | ||
+ | pen.y += slot->advance.y; | ||
+ | } | ||
+ | fprintf(stderr, qPrintable("\n")); | ||
+ | |||
+ | for (int y = 0; y < HEIGHT; y++) { | ||
+ | for (int x = 0; x < WIDTH; x++) { | ||
+ | QImage_Canvas.setPixel(x, y, qRgb(canvas[y][x], canvas[y][x], canvas[y][x])); | ||
+ | } | ||
+ | } | ||
+ | QImage_Canvas.save(QString_ImageFileName, "BMP"); | ||
+ | printf(qPrintable("P3\n%d %d\n%d\n"), WIDTH, HEIGHT, 255); | ||
+ | |||
+ | if (QFile_Text.open(QIODevice::WriteOnly)) { | ||
+ | QTextStream out(&QFile_Text); | ||
+ | for (int y = 0; y < HEIGHT; y++) { | ||
+ | for (int x = 0; x < WIDTH; x++) { | ||
+ | if (canvas[y][x]) { | ||
+ | QString_ImageRowMonoBuffer = QString_ImageRowMonoBuffer + " 1"; | ||
+ | //printf(qPrintable("%2d"), 1); | ||
+ | } | ||
+ | else { | ||
+ | QString_ImageRowMonoBuffer = QString_ImageRowMonoBuffer + " 0"; | ||
+ | //printf(qPrintable("%2d"), 0); | ||
+ | } | ||
+ | } | ||
+ | QString_ImageRowMonoBuffer = QString_ImageRowMonoBuffer + "\n"; | ||
+ | //printf(qPrintable("\n")); | ||
+ | |||
+ | out << QString_ImageRowMonoBuffer; | ||
+ | |||
+ | QString_ImageRowMonoBuffer = ""; | ||
+ | |||
+ | } | ||
+ | QFile_Text.close(); | ||
+ | } | ||
+ | else { | ||
+ | QString QString_Error(qPrintable("No Open or Create Error\n")); | ||
+ | QString_Error = QString_Error + QFile_Text.errorString(); | ||
+ | fprintf(stderr, qPrintable(QString_Error)); | ||
+ | } | ||
+ | delete[] idces; | ||
+ | FT_done_Face (face); | ||
+ | FT_Done_FreeType (library); | ||
+ | return a.exec(); | ||
+ | } | ||
+ | </Syntaxhighlight2> | ||
+ | |||
+ | 出力結果ファイル | ||
+ | |||
+ | [[ファイル:FreeTypeStep02Test.bmp|400px|thumb|none|実行結果]] | ||
+ | |||
+ | |||
+ | 今回は公式サイトの最初のチュートリアルをQtConsoleApp版にしたものです。うごきも少し違う工夫がなされていて、下への移動のつもりは、下から上に書いていくチュートリアルになってしまっていました。キャンバスへの引数はY座標が逆転する要素があるようになっていますね。解説はしません。あと勉強になったのは2点 | ||
+ | |||
+ | *プログラムの最後の方にある。オブジェクトの消滅作業ですね。 | ||
+ | : Initによる初期化で動的に生成されたオブジェクトをこの二つの方法で綺麗に掃除しています。これが行儀のいい書き方なのですね。もっと大きいソリューションとして構築する場合はデストラクタとかで処理するようにしたいものです。 | ||
+ | |||
+ | *グリフ番号を取得しなくてもFT_Load_Charで、ユニコード番号だけで、グリフのビットマップが得られる方法がある。 | ||
+ | : 最初のチュートリアルでグリフ番号を取得してグリフのビットマップを取得していました。PDFを作るという目的においては重要ですが、グリフを描画するという処理だけならあまり意識はしたくない処理が省けるというのもいい感じです。おもしろい。 | ||
+ | |||
+ | |||
+ | freetypeで独自のフォントの変形処理を作るのが醍醐味なのかもしれません。ここまでのチュートリアルがわかっただけでも、相当いろいろな変形処理が作れそう。任意の半径の丸い円弧状にそって文字を配置したりもできるようになりそうです。出来栄えを確かめたりする作業をもった素早くするには、それなりのユーザインタフェースを作らないと駄目ですね。ややこしそう。画像をあやつる一歩を踏み出した感じ。いいね。この例にも文字を変形させる処理があります。FT_Set_Transformという関数とそのちょっとまえのFT_Matrixというクラス変数のmatrixというオブジェクトの準備作業が必要なようです。angleの計算の割り算の優先度を上げているところでカッコの中の数字のどちらかに小数点がないと、なにをやっても計算結果が整数の 0 になって、angleは常に 0 になってしまいます。注意されたし。 | ||
+ | |||
+ | |||
+ | グリフ毎にある要素の意味は以下のサイトで画像付きで表記されていて、わかりやすいですね。これが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(¶ms, 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, ¶ms); | ||
+ | } | ||
+ | |||
+ | 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(¶ms, 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, ¶ms); | ||
+ | |||
+ | 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, ¶ms); | ||
+ | } | ||
+ | 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 構造解析]]に戻る。 |