[BlueLeaf1336]> PROBLEMS> FontStudy>

フォント勉強会その2

historyTOP

2009/12/02:作成

GetGlyphOutlineTOP

あまりに進まないので、フォントの勉強になってないんですが、前回テキスト(Delph Win32 Graphics API リファレンス)を丸写ししたままになっていたGetGlyphOutlineをもう少し突っ込んで試してみることにしました。って読み返してみると妄想でした。ローカルでテストしただけで前回なんてありませんでした。

ネットでGetGlyphOutlineを検索してみるとアンチエイリアスのかかった文字を出力できるということでなんかすごそうです。ちなみにテキストは古いせいかアンチエイリアスバージョンの紹介がされてないので、やってみることにしました。

GetGlyphOutlineで得られる情報は、実行時に渡すパラメータで切り替えることができます。気になるパラメータもあるんですが、ここでは、モノクロビットマップ、アンチエイリアス(小)、アンチエイリアス(中)、アンチエイリアス(大)を試します。

以下、実行結果です。

パラメータ(の1つ) GGO_BITMAP GGO_GRAY2_BITMAP GGO_GRAY4_BITMAP GGO_GRAY8_BITMAP
得られたビットマップ

ま、見たとおり失敗してます。原因もはっきりしています。以下、ソースですが、得られた結果からビットマップを作るときにナニカをいじらないとだめだと思います。というか作り方自体も変えないとだめかも。しかしその方向で攻めるのはしんどい。Bitmap関係のAPIってどっさりあって理解する気になりません。

//-----------------------------------------------------------------------------
//  Subject: [Delphi-ML:6963] Re: WMF from TT kanji font
//  Subject: [Delphi:85056] Re: TFileStreamでのString読み書き
procedure TForm1.Button1Click(Sender: TObject);
var
    LMat2: TMat2;
    LMetrics: TGlyphMetrics;
    LDC: HDC;
    LChar: String;
    LFormat: Cardinal;
    LSize: Cardinal;
    LGlyph: HBITMAP;
    LBitmap: Windows.TBitmap;
var
    LFileName: String;
    i: Integer;
    LStream: TFileStream;
    LBuf: array of Byte;
    LText: ShortString;
begin
    //  構造体初期化
    FillChar(LMat2, SizeOf(LMat2), 0);
    FillChar(LMetrics, SizeOf(LMetrics), 0);

    //  回転角は回転なしに設定
    LMat2.eM11.Value := 1;
    LMat2.eM22.Value := 1;

    //  デバイスコンテキスト
    LDC := Self.Canvas.Handle;
    //  出力する文字
    LChar := 'あ';
    //  出力フォーマット
    LFormat := GGO_BITMAP;
    //LFormat := GGO_GRAY2_BITMAP;
    //LFormat := GGO_GRAY4_BITMAP;
    //LFormat := GGO_GRAY8_BITMAP;

    //  1回目の呼出=メモリサイズ取得
    LSize := GetGlyphOutline(LDC, OrdEx(LChar), LFormat, LMetrics, 0, nil, LMat2);

    //  指定されたサイズのメモリ確保
    SetLength(LBuf, LSize);

    //  ビットマップ取得
    LSize := GetGlyphOutline(LDC, OrdEx(LChar), LFormat, LMetrics, LSize, LBuf, LMat2);

    //  ビットマップ情報作成
    LBitmap.bmType := 0;
    LBitmap.bmWidth := (LMetrics.gmBlackBoxX + 31) and not 31;
    LBitmap.bmHeight := LMetrics.gmBlackBoxY;
    LBitmap.bmWidthBytes := LBitmap.bmWidth shr 3;
    LBitmap.bmPlanes := 1;
    LBitmap.bmBitsPixel := 1;
    LBitmap.bmBits := LBuf;

    //  ビットマップ作成
    LGlyph := CreateBitmapIndirect(LBitmap);

    //  TImage に表示
    Image1.Picture.Bitmap.Handle := LGlyph;
    Image1.Picture.Bitmap.Width := LMetrics.gmBlackBoxX;

    //  内容をファイルに保存(文字コードと出力形式で区別)
    LFileName := ChangeFileExt(ParamStr(0), '')
              + Format('%s_%3.3d', [IntToHex(OrdEx(LChar), 4), LFormat]);
    //  ビットマップを保存
    Image1.Picture.Bitmap.SaveToFile(LFileName + '.bmp');
    //  ビットマップ情報を保存
    FStrBuf.Clear();
    FStrBuf.Add(Format('GetGlyphOutline     = %d', [LSize]));
    FStrBuf.Add(Format('Metrics.gmBlackBoxX = %d', [LMetrics.gmBlackBoxX]));
    FStrBuf.Add(Format('Metrics.gmBlackBoxY = %d', [LMetrics.gmBlackBoxY]));
    FStrBuf.Add(Format('Bitmap.bmWidth      = %d', [LBitmap.bmWidth]));
    FStrBuf.Add(Format('Bitmap.bmHeight     = %d', [LBitmap.bmHeight]));
    FStrBuf.Add(Format('Bitmap.bmWidthBytes = %d', [LBitmap.bmWidthBytes]));
    FStrBuf.SaveToFile(LFileName + '.log');
    LStream := TFileStream.Create(LFileName + '.txt', fmCreate);
    for i := 0 to High(LBuf) do
    begin
        LText := IntToHex(LBuf[i], 2) + ' ';
        LStream.Write(LText[1], Length(LText));
    end;
    LStream.Free();
end;

ということで、個人的にやる気の出せる方向性を探してみました。現時点でDelphiに関して唯一Googleに対抗できるローカル情報源(いいすぎ?)「Delphi-ML過去ログ」を検索すると、こんなコメントがあります。

Subject: [Delphi-ML:6963] Re: WMF from TT kanji font
(略)
とりあえず以下はビットマップで取り出すコードです。
(略)
これで表示されるコードをプロットしてゆくと「あ」が
現れました。面白い!!
(略)

テキストでは、GetGlyphOutlineの実行結果をそのままWindows.TBitmap.bmBitsに突っ込んで、CreateBitmapIndirectに渡しています。しかし、上記のレスでは実行結果がそのまま使えるといっています。

実は、上記のソースはそれを知った後で書いているので、ファイルにWindows.TBitmap.bmBitsに渡す内容をバイトごとに16進数表記で出力しています。以下ではその中身について調べてみることにします。もうなんか、話の流れがグダグダになってきてる気がしますが押し切ります。

問題の単純化TOP

とりあえず、「あ」を目視で調べるのはしんどいので、もっとシンプルな文字で試すことにします。もちろん「i」です。しかもゴシックの。
※次の表で、GGO_BITMAPのビットマップだけ背景が濃い理由は、背景が白いと文字があるのかないのかわからないためです。

パラメータ(の1つ) GGO_BITMAP GGO_GRAY2_BITMAP GGO_GRAY4_BITMAP GGO_GRAY8_BITMAP
得られたビットマップ
データサイズ 404122412241236
Metrics.gmBlackBoxX11111112
Metrics.gmBlackBoxY101102102103
Bitmap.bmWidth 32323232
Bitmap.bmHeight 101102102103
Bitmap.bmWidthBytes4444

見たとおりビットマップは最初の1つ以外腐っていますが、今回はそれぞれのビットマップにまつわる数値を一緒に表示しています。それぞれの数字に関する現時点の理解は以下のとおり。というかテキストに説明書いてあるんですが...

データサイズそのまま。ソースでいうと LBuf のサイズ
Metrics.gmBlackBoxXビットマップの実際の幅
Metrics.gmBlackBoxYビットマップの実際の高さ
Bitmap.bmWidthワード境界がどうの、という理由で実際の幅よりも大きめ(※1)
Bitmap.bmHeightMetrics.gmBlackBoxY に同じ
Bitmap.bmWidthBytes各走査行のバイト数?

※1 実際のビットマップの幅がどうであれ、箱のサイズは飛び飛びの値しかとれない。データの中身を見た感じわかりそうなので、後回し。

つまるところ、横幅が微妙ということだけ覚えといて、次に進みます。

GGO_BITMAP の場合TOP

タイトルのとおりです。先ほどのソースコードで出力したものが以下。

FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00 FF E0 00 00

ブラウザの幅を狭くしてみると、なんとなく「i」が見えてきます。最初に「FF E0 00 00」が繰り返されていて、しばらく「00 00 00 00」が続いて、再び「FF E0 00 00」の繰り返し。いい感じです。

テキストのGetGlyphOutlineの項のGGO_BITMAPの説明には「グリフのアウトラインを、ダブルワードでそろえられた、行並びのモノクロビットマップ形式で取得する」とあります。ダブルワード(DWORD) はWORDの2倍、WORDByteの2倍、つまり、ダブルワードは4バイトです。繰り返しの数も4。関係ある気がします。

とにかく、さっきのべったり並んだデータを繰り返し位置で改行してみます。

FF E0 00 00 (が13回) = 「i」の点
00 00 00 00 (が17回) = 「i」の隙間
FF E0 00 00 (が71回) = 「i」の棒

合計は101行、画像の高さ(Metrics.gmBlackBoxY)も101。あってます。次に、16進数表記を2進数表記にかえるとこうなります。

11111111 11100000 00000000 00000000 (が13回) = 「i」の点
00000000 00000000 00000000 00000000 (が17回) = 「i」の隙間
11111111 11100000 00000000 00000000 (が71回) = 「i」の棒

最初の行(「i」の点の部分)は1が8+3=11個並んで、残りはゼロ。
隙間はオールゼロ
最後の行(「i」の棒)も、1が8+3=11個並んで、残りはゼロ。

これって、画像の幅(Metrics.gmBlackBoxX)の11です。幸せ。

また、各行は4バイトですが、4*8=32bitなので、これは Bitmap.bmWidth のことですね。
残るBitmap.bmWidthBytesは、各行4バイトの4かな? 名前的にもそれっぽい。

ゼロが白、1が黒とすればちゃんと「i」になります。

GGO_GRAY2_BITMAP の場合TOP

もうひとついってみます。次はアンチエイリアス(小)の場合です。

02 02 02 02 02 02 02 02 02 02 02 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00 04 04 04 04 04 04 04 04 04 04 04 00

今度もブラウザの幅を狭めてみると「i」が見えてきます。何個かおきにゼロが混ざっていますが、ここが区切りのようです。

先ほどと同じように数えてみます。

02 02 02 02 02 02 02 02 02 02 02 00 (1回)
04 04 04 04 04 04 04 04 04 04 04 00 (13回)
00 00 00 00 00 00 00 00 00 00 00 00 (17回)
04 04 04 04 04 04 04 04 04 04 04 00 (71回)

12個(12バイト)ずつ分けると102行。Metrics.gmBlackBoxY と同じです。各行は、12バイトなので、GGO_BITMAPのときが4バイトだったことを考えると、3倍になってます。

また、各行の最後のバイトはゼロで埋まっていて、Metrics.gmBlackBoxX(画像の幅)が11だから、1バイトが1ピクセルをあらわしていることになります。そうすると、Bitmap.bmWidthBytesが12になるべきなのかも。ということは、Bitmap.bmWidthBytes=データサイズ÷Metrics.gmBlackBoxYで計算するのかも。

上のソースコードを触ってみると、GGO_GRAY_系の結果がすこーしだけ変化しました。変化しただけでやっぱり腐ってますけど。

ところで、GGO_BITMAPはモノクロだったので色は0か1でした。今回はGGO_GRAY2_BITMAPなので、色データが2ビットで表現される、つまり00 01 10 11(2進数) の4色表示ってことかな。でもそれだと4進数なので、出てくる数字は00 01 02 03(16進数)のどれかのはずなのに、データには00 02 04(16進数)が見えます。よくわかりません。

PlatformSDK をよんでみると、確かにそのとおりに書いてます。range from 0 to 4。0は白、4は黒とすると、残りはどうなるんだろう。17階調? とか 65階調? もよくわからん。これはオフラインじゃあ無理やな。

The glyph bitmap returned by GetGlyphOutline

when GGO_BITMAP is specified is a
    DWORD-aligned,
    row-oriented,
    monochrome bitmap.
When GGO_GRAY2_BITMAP is specified, the bitmap returned is a
    DWORD-aligned,
    row-oriented array of bytes whose values range from 0 to 4.
When GGO_GRAY4_BITMAP is specified, the bitmap returned is a
    DWORD-aligned,
    row-oriented array of bytes whose values range from 0 to 16.
When GGO_GRAY8_BITMAP is specified, the bitmap returned is a
    DWORD-aligned,
    row-oriented array of bytes whose values range from 0 to 64.

無駄なデータ引用で無駄に長いページになってしまいました。無駄なのでこのページはここまでにします。

EOFTOP