Help us understand the problem. What is going on with this article?

.NET GraphicsPath.AddStringで縁取り文字を描きたい

GraphicsPathクラスで文字のパスを追加して、縁取りを追加してから文字を描きたい。

// ※using、Dispose文は省略
var bitmap = new Bitmap(1280, 200);
// 透過
bitmap.MakeTransparent();
// BitmapからGraphicsを生成
var graphics = Graphics.FromImage(bitmap);

var stringFormat = new StringFormat();
// どんなに長くて単語の区切りが良くても改行しない
stringFormat.FormatFlags = StringFormatFlags.NoWrap;
// どんなに長くてもトリミングしない
stringFormat.Trimming = StringTrimming.None;
// ハイクオリティレンダリング
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
// アンチエイリアスをかける
graphics.SmoothingMode = SmoothingMode.HighQuality;

// GraphicsPathを生成
var gp = new GraphicsPath();

// パスに文字を追加
gp.AddString("山路を登りながら、智に働けば角が立つ。", new FontFamily("メイリオ"), 0, 46, new Point(8, 8), stringFormat);

// 縁取りをする。
graphics.DrawPath(new Pen(Color.Black, 16), gp);
// 文字を塗りつぶす。
graphics.FillPath(new SolidBrush(Color.White), gp);

// テスト用にBitmapの内容をD:\result.pngに出力
bitmap.Save("D:\\result.png", System.Drawing.Imaging.ImageFormat.Png);

注意して欲しいのが、縁取りする場合の文字の座標。文字のパスを追加した座標を基準に、中央から太さが変わっていくので文字の座標をを縁取りのサイズの半分ずらす必要がある。上のサンプルでは、縁取りのサイズが16なので文字自体をnew Point(8, 8)としている。

レンダリング結果

result.png

「智に働けば"角が"立つ」、ってね!

やかましいわ! うおー! なんかおかしい!

文字自体のパスは正常に追加されてるが、GraphicsPath.DrawPathによる縁取りの描画が破綻しているっぽい。

フォントによって結果が異なりますが(ハミングとかスーラとか、角丸系のフォントは大丈夫)、角張ってるフォントはこうなりやすい……。

PenクラスのLineJoinプロパティとMiterLimitプロパティ

https://docs.microsoft.com/ja-jp/dotnet/api/system.drawing.pen

マイクロソフトのドキュメントの登場だー! Penクラスのプロパティ弄れば解決できるかな、と思いとりあえずこのページを観てみる。すると「LineJoin - この Pen で描画された連続する 2 本の直線の終点の接合スタイルを取得または設定します。」といういかにもそれっぽいプロパティを見つけたのでこの列挙型を変更してみる。

LineJoin.Bevel

graphics.DrawPath(new Pen(Color.Black, 16) { LineJoin = LineJoin.Bevel }, gp);

result.png

斜めの縁取りが生成される感じ。3DCGのベベルエッジと同じ感じですね。

LineJoin.Miter

graphics.DrawPath(new Pen(Color.Black, 16) { LineJoin = LineJoin.Miter }, gp);

result.png

LineJoin.Miter = 0なのでこれがデフォルト。

LineJoin.MiterClipped

graphics.DrawPath(new Pen(Color.Black, 16) { LineJoin = LineJoin.MiterClipped }, gp);

result.png

これもLineJoin.Miterとほとんど同じなんだけど、MiterLimitの値を上回ったときの処理方法が違うっぽい。

graphics.DrawPath(new Pen(Color.Black, 16) { LineJoin = LineJoin.Miter, MiterLimit = 2 }, gp);
graphics.DrawPath(new Pen(Color.Black, 16) { LineJoin = LineJoin.MiterClipped, MiterLimit = 2 }, gp);

result.png

違うはず……なんだけど違いがよく分かんねぇわ。フォトショで減算したらたしかに違うって事はわかるけど。

LineJoin.Round

graphics.DrawPath(new Pen(Color.Black, 16) { LineJoin = LineJoin.Round} , gp);

result.png

個人的に1番好き。角張っていないので他のと比べて優しい感じ。

まとめ

  • カクカクした縁取りを行うなら、LineJoin.Miterを指定して適切なMiterLimitを指定する。
  • 丸い縁取りを行うなら、LineJoin.Roundを指定する。
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away