2016/04/05

[C#]Graphics.DrawStringとGraphicsPath.AddStringのサイズを合わせる

このエントリーをはてなブックマークに追加
さて。
Graphics.DrawStringメソッドとGraphicsPath.AddStringメソッドはどちらも文字の描画を扱うときに使うメソッドです。
困りごととして、フォントサイズの指定の仕様がそれぞれで異なっていることです。

この記事は、以前に書いた[C#]文字のアウトラインを描くへの補足です。

Graphics.DrawStringメソッドの引数は、Graphics.DrawString(String, Font, Brush, PointF, StringFormat)という指定に対して、GraphicsPath.AddStringメソッドは、GraphicsPath.AddString(String, FontFamily, Int32, Single, Point, StringFormat)です。
Graphics.DrawStringメソッドの呼び出しのFontオブジェクトのフォントサイズをそのまま、GraphicsPath.AddStringメソッドの4つ目の引数に指定すると、AddStringのほうが一回り小さく描画されます。

どちらのメソッドにも72fを指定したときの描画は下のようになります。青がGraphics.DrawString、オレンジがGraphicsPath.AddStringです。

Graphics.DrawStringメソッドとGraphicsPath.AddStringメソッドのフォントサイズの指定には、単位に違いがあり、そのため同じ数値を指定しても出力が異なってしまうようです。

Graphics.DrawStringメソッドに指定するFontは通常Point単位に対して、GraphicsPath.AddStringメソッドに指定するemSizeはPixel単位なのでした。
ということで、単位を合わせるために、変換してあげればよいわけです。

調べたうちでは、3つの方法がありました。
  1. font.Heightプロパティからemサイズを計算する
  2. font.SizeInPointをPixel単位に変換する
  3. FontオブジェクトをPixel単位で生成する
ただし、シビアな精度ではいずれも万能ではありません。
数値計算の誤差だと思いますがサイズによって、またフォントによっても、わずかながらズレが出ますのでご注意ください。

1.font.Heightプロパティからemサイズを計算する

ほしいのはpixel単位のemサイズです。

方法 : フォント メトリックを取得する‎
https://social.msdn.microsoft.com/Search/ja-JP?query=%E3%83%95%E3%82%A9%E3%83%B3%E3%83%88%20%E3%83%A1%E3%83%88%E3%83%AA%E3%83%83%E3%82%AF%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%E2%80%8E%20&pgArea=header&emptyWatermark=true&ac=4

emサイズとは、文字がすっぽりおさまる正方形のサイズです。上の図でいうと、アセントとディセントに少しのスキマを足した大きさになります。
Fontオブジェクトのfont.Heightプロパティは、文字の高さ(一行分の高さ)をPixel単位で取れます。図では、行間に相当します。
これをもとに、Pixcel単位のフォントのemサイズを計算してあげます。

font.GetEmHeightメソッドは、フォントのデザイン単位がとれます。デザイン単位とは、emサイズを2048としたとき(フォントによりますが)の、メトリックのサイズ情報です。同じくデザイン単位の行間はfont.GetLineSpacingメソッドを使って取得できます。

float emSize = (float)font.Height * font.FontFamily.GetEmHeight(font.Style) / font.FontFamily.GetLineSpacing(font.Style);

これでPixel単位のemサイズが取れました。

ソースコード
// using System.Drawing;
string str = "Hello World!";

Graphics g = this.pictureBox1.CreateGraphics();
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
g.SmoothingMode = SmoothingMode.HighQuality;

PointF point = new PointF(5.0f, 15.0f); // 文字を描画する原点(左上)

float fontSize = 72f;
FontFamily fontFamily = new FontFamily("メイリオ");

// Graphics.DrawStringによる描画
Font font = new Font(fontFamily, fontSize); // →単位がPoint
g.DrawString(str, font, Brushes.LightBlue, point);

// GraphicsPath.AddStringによる描画
float emSize = (float)font.Height * font.FontFamily.GetEmHeight(font.Style) / font.FontFamily.GetLineSpacing(font.Style);

GraphicsPath path = new GraphicsPath();
StringFormat format = new StringFormat();
path.AddString(str, font.FontFamily, (int)font.Style, emSize, point, format);
g.DrawPath(Pens.OrangeRed, path);

g.Dispose();

実行結果
これを実行させると、このようになります。

ばっちり、ぴったりです。
青く塗られているほうがGraphics.DrawString、オレンジのフチがGraphicsPath.AddStringによる描画です。

2.font.SizeInPointをPixel単位に変換する

PointをPixelに換算する方法です。
1inch=72pointです。一度、inchに置き換えてから、pixelに変換する方法で計算できます。

float emSize = (float)g.DpiY * font.SizeInPoints / 72.0f;

3.FontオブジェクトをPixel単位で生成する

Fontオブジェクトの生成時に、単位を指定することができます。
通常、単位を省略すると単位はpointになるのですが、GraphicsUnit.Pixelを指定することで、最初からpixcel単位のフォントサイズにしておくことができます。

ソースコード
// using System.Drawing;
string str = "Hello World!";

Graphics g = this.pictureBox1.CreateGraphics();
g.SmoothingMode = SmoothingMode.HighQuality;

PointF point = new PointF(5.0f, 15.0f); // 文字を描画する原点(左上)

float fontSize = 96f;
FontFamily fontFamily = new FontFamily("メイリオ");

// Graphics.DrawStringによる描画
Font font = new Font(fontFamily, fontSize, GraphicsUnit.Pixel); // →単位がPixel
g.DrawString(str, font, Brushes.LightBlue, point);

// GraphicsPath.AddStringによる描画
GraphicsPath path = new GraphicsPath();
StringFormat format = new StringFormat();
path.AddString(str, font.FontFamily, (int)font.Style, font.Size, point, format);
g.DrawPath(Pens.OrangeRed, path);

g.Dispose();

72pointの指定は、Pixel換算だと96pxなので、fontSizeは96にしています。

わずかなズレが出ます

フォントによって、まちまちでズレが出ました。
Georgiaフォントを使用して、「1.font.Heightプロパティからemサイズを計算する」で描画。

Georgiaフォントを使用して、「2.font.SizeInPointをPixel単位に変換する」で描画。
算出方法によって、こちらはぴったり。

また大きいサイズだとぴったりなのですが、小さいサイズを指定するとぴったりにならずにズレが出ることがあります。

フォントサイズ 72ポイント、「2.font.SizeInPointをPixel単位に変換する」で描画。

フォントサイズ 36ポイント、「2.font.SizeInPointをPixel単位に変換する」で描画。

内部の計算誤差なのかなあ。フォントによるズレも、フォントサイズの指定を大きくすることで軽減されるようです。
誤差としてはたぶんだいたい1pixel以内に収まるものなのかなあと思います。
というわけで、いずれも精度的には万能ではありませんので、シビアな見当が必要な方はご注意ください…。

はい、以上です。
文字の描画サイズについてでした!

Font in 'GraphicsPath.AddString' is smaller than usual font
http://stackoverflow.com/questions/2292812/font-in-graphicspath-addstring-is-smaller-than-usual-font

0 件のコメント :

コメントを投稿