DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

文字列を描画したときの大きさを計測する

注意:画像の表示方法が分からないという方は、まず「コントロールやフォームに画像を表示する」をご覧ください。また、文字列を描画する方法は、「文字を描く」をご覧ください。

MeasureStringやMeasureCharacterRangesメソッドを使用する方法

Graphics.DrawStringメソッドで文字列を描画したときの大きさを計測するには、Graphics.MeasureStringメソッドを使います。しかしMeasureStringメソッドはグリフの突出に備えて前後に余白を入れますので、実際に文字列を描画したときの大きさよりも通常は大きく計測されます。

これとは別に、Graphics.MeasureCharacterRangesメソッドを使って計測することもできます。MeasureCharacterRangesメソッドでは、文字列内のどの範囲を計測するか(例えば、3文字目から6文字目までなど)を指定して、大きさを計測できます。計測する範囲は複数指定でき、文字列の大きさがRegion配列で返されます。

通常は、MeasureCharacterRangesメソッドを使った方がより実際の大きさに近い値を返します。しかしこの場合も余白はありますし、フォントのスタイルが太字や斜体だった場合は実際の大きさより狭く計測されることもあります。

補足:MSDN(Graphics.MeasureStringメソッド)には、次のように書かれています。

「(たとえば、書式設定されたテキストを実装するときに) レイアウト内の隣接する文字列に適したメトリックを取得するには、MeasureCharacterRanges メソッドを使用するか、または StringFormat を受け取る MeasureString メソッドのうちの 1 つを使用し、GenericTypographic を渡します。 また、Graphics の TextRenderingHint が AntiAlias である必要があります。」

つまり、MeasureStringメソッドを呼び出すときにStringFormat.GenericTypographicプロパティを渡すと、MeasureCharacterRangesメソッドと同じ結果を得られるようです。また、Graphics.TextRenderingHintプロパティをAntiAliasにした方がより正確な値を得られるかもしれません。
補足:計測する文字列の前後に空白文字があり、これを無視したくない場合は、StringFormat.FormatFlagsプロパティにMeasureTrailingSpacesを加えてください。

下のコードでは、"DOBON.NET"という文字列を描画しています。そして、この文字列の大きさをMeasureStringメソッドで計測した結果を赤い四角で描画し、さらに、"DOBON"と"NET"の大きさをMeasureCharacterRangesメソッドで計測した結果を青い四角で描画しています。結果は、PictureBoxコントロール"PictureBox1"に表示しています。

MeasureStringとMeasureCharacterRangesメソッドで計測した文字列の大きさ

VB.NET
コードを隠すコードを選択
'Imports System.Drawing

'表示する文字列
Dim s As String = "DOBON.NET"

'描画先とするImageオブジェクトを作成する
Dim canvas As New Bitmap(PictureBox1.Width, PictureBox1.Height)
'ImageオブジェクトのGraphicsオブジェクトを作成する
Dim g As Graphics = Graphics.FromImage(canvas)

'フォントオブジェクトの作成
Dim fnt As New Font("Arial", 25)
'StringFormatオブジェクトの作成
Dim sf As New StringFormat

'文字列を描画する
g.DrawString(s, fnt, Brushes.Black, 0, 0, sf)

'幅の最大値が1000ピクセルとして、文字列を描画するときの大きさを計測する
Dim stringSize As SizeF = g.MeasureString(s, fnt, 1000, sf)
'取得した文字列の大きさを使って四角を描画する
g.DrawRectangle(Pens.Red, 0, 0, stringSize.Width, stringSize.Height)

'"DOBON"と"NET"の大きさを計測する
'計測する文字の範囲を指定する
Dim characterRanges As CharacterRange() = _
    {New CharacterRange(0, 5), New CharacterRange(6, 3)}
sf.SetMeasurableCharacterRanges(characterRanges)
'文字列のレイアウト四角形を指定する
Dim layoutRect As New RectangleF(0, 0, 500, 100)
'文字列に外接するRegion配列を取得する
Dim stringRegions As Region() = _
    g.MeasureCharacterRanges(s, fnt, layoutRect, sf)
'取得した文字列の大きさを使って四角を描画する
Dim rect1 As RectangleF = stringRegions(0).GetBounds(g)
g.DrawRectangle(Pens.Blue, Rectangle.Round(rect1))
Dim rect2 As RectangleF = stringRegions(1).GetBounds(g)
g.DrawRectangle(Pens.Blue, Rectangle.Round(rect2))

'リソースを解放する
fnt.Dispose()
sf.Dispose()
g.Dispose()

'PictureBox1に表示する
PictureBox1.Image = canvas
C#
コードを隠すコードを選択
//using System.Drawing;

//表示する文字列
string s = "DOBON.NET";

//描画先とするImageオブジェクトを作成する
Bitmap canvas = new Bitmap(PictureBox1.Width, PictureBox1.Height);
//ImageオブジェクトのGraphicsオブジェクトを作成する
Graphics g = Graphics.FromImage(canvas);

//フォントオブジェクトの作成
Font fnt = new Font("Arial", 25);
//StringFormatオブジェクトの作成
StringFormat sf = new StringFormat();

//文字列を描画する
g.DrawString(s, fnt, Brushes.Black, 0, 0, sf);

//幅の最大値が1000ピクセルとして、文字列を描画するときの大きさを計測する
SizeF stringSize = g.MeasureString(s, fnt, 1000, sf);
//取得した文字列の大きさを使って四角を描画する
g.DrawRectangle(Pens.Red, 0, 0, stringSize.Width, stringSize.Height);

//"DOBON"と"NET"の大きさを計測する
//計測する文字の範囲を指定する
CharacterRange[] characterRanges =
    {new CharacterRange(0, 5),
    new CharacterRange(6, 3)};
sf.SetMeasurableCharacterRanges(characterRanges);
//文字列のレイアウト四角形を指定する
RectangleF layoutRect = new RectangleF(0, 0, 500, 100);
//文字列に外接するRegion配列を取得する
Region[] stringRegions = g.MeasureCharacterRanges(s, fnt, layoutRect, sf);
//取得した文字列の大きさを使って四角を描画する
RectangleF rect1 = stringRegions[0].GetBounds(g);
g.DrawRectangle(Pens.Blue, Rectangle.Round(rect1));
RectangleF rect2 = stringRegions[1].GetBounds(g);
g.DrawRectangle(Pens.Blue, Rectangle.Round(rect2));

//リソースを解放する
fnt.Dispose();
sf.Dispose();
g.Dispose();

//PictureBox1に表示する
PictureBox1.Image = canvas;

TextRenderer.MeasureTextメソッドを使用する方法

.NET Framework 2.0から追加されたTextRenderer.DrawTextメソッドを使用して文字列を描画するときは、TextRenderer.MeasureTextメソッドを使って文字列の大きさを計測します。

このメソッドもグリフの突出に備えて余白を入れますので、実際に描画される大きさよりも通常は大きく計測されます。余白なしで計測(および描画)をするには、MeasureTextメソッド(およびDrawTextメソッド)を呼び出すときにTextFormatFlags.NoPaddingを指定します。

以下の例では、DrawTextメソッドで文字列を描画した後で、MeasureTextメソッドで計測した大きさで赤い四角を描画しています。また、その下にはTextFormatFlags.NoPaddingを指定して文字列を描画しています。

MeasureTextメソッド

VB.NET
コードを隠すコードを選択
'Imports System.Drawing
'Imports System.Windows.Forms

'表示する文字列
Dim s As String = "DOBON.NET"

'描画先とするImageオブジェクトを作成する
Dim canvas As New Bitmap(PictureBox1.Width, PictureBox1.Height)
'ImageオブジェクトのGraphicsオブジェクトを作成する
Dim g As Graphics = Graphics.FromImage(canvas)

'フォントオブジェクトの作成
Dim fnt As New Font("Arial", 25)

'文字列を描画する
TextRenderer.DrawText(g, s, fnt, New Point(0, 0), Color.Black)
'文字列を描画するときの大きさを計測する
Dim strSize As Size = TextRenderer.MeasureText(g, s, fnt)
'取得した文字列の大きさを使って四角を描画する
g.DrawRectangle(Pens.Red, 0, 0, strSize.Width, strSize.Height)

'NoPaddingにして、文字列を描画する
TextRenderer.DrawText(g, s, fnt, New Point(0, 50), Color.Black, _
    TextFormatFlags.NoPadding)
'大きさを計測して、四角を描画する
Dim nopadSize As Size = TextRenderer.MeasureText(g, s, fnt, _
    New Size(1000, 1000), TextFormatFlags.NoPadding)
g.DrawRectangle(Pens.Blue, 0, 50, nopadSize.Width, nopadSize.Height)

'リソースを解放する
fnt.Dispose()
g.Dispose()

'PictureBox1に表示する
PictureBox1.Image = canvas
C#
コードを隠すコードを選択
//using System.Drawing;
//using System.Windows.Forms;

//表示する文字列
string s = "DOBON.NET";

//描画先とするImageオブジェクトを作成する
Bitmap canvas = new Bitmap(PictureBox1.Width, PictureBox1.Height);
//ImageオブジェクトのGraphicsオブジェクトを作成する
Graphics g = Graphics.FromImage(canvas);

//フォントオブジェクトの作成
Font fnt = new Font("Arial", 25);

//文字列を描画する
TextRenderer.DrawText(g, s, fnt, new Point(0, 0), Color.Black);
//文字列を描画するときの大きさを計測する
Size strSize = TextRenderer.MeasureText(g, s, fnt);
//取得した文字列の大きさを使って四角を描画する
g.DrawRectangle(Pens.Red, 0, 0, strSize.Width, strSize.Height);

//NoPaddingにして、文字列を描画する
TextRenderer.DrawText(g, s, fnt, new Point(0, 50), Color.Black,
    TextFormatFlags.NoPadding);
//大きさを計測して、四角を描画する
Size nopadSize = TextRenderer.MeasureText(g, s, fnt,
    new Size(1000, 1000), TextFormatFlags.NoPadding);
g.DrawRectangle(Pens.Blue, 0, 50, nopadSize.Width, nopadSize.Height);

//リソースを解放する
fnt.Dispose();
g.Dispose();

//PictureBox1に表示する
PictureBox1.Image = canvas;

実際に描画して計測する方法

今まで紹介した方法では、どの方法でもどんぴしゃりな大きさを計測することはできません。正確な大きさを知るには、結局は実際に描画してみるしかないようです。

そのようにして計測する例が、「MeasureString and DrawString difference」にあります。これを参考にさせていただいて、私も以下のようなコードを書いてみました。この例では、DrawStringメソッドで描画した時の大きさを計測しています。やっていることは、Bitmapに文字列を描画して、Bitmap.GetPixelメソッドで1つ1つのピクセルの色を調べているだけです。

VB.NET
コードを隠すコードを選択
'Imports System.Drawing

''' <summary>
''' Graphics.DrawStringで文字列を描画した時の大きさと位置を正確に計測する
''' </summary>
''' <param name="g">文字列を描画するGraphics</param>
''' <param name="text">描画する文字列</param>
''' <param name="font">描画に使用するフォント</param>
''' <param name="proposedSize">これ以上大きいことはないというサイズ。
''' できるだけ小さくすること。</param>
''' <param name="stringFormat">描画に使用するStringFormat</param>
''' <returns>文字列が描画される範囲。
''' 見つからなかった時は、Rectangle.Empty。</returns>
Public Shared Function MeasureStringPrecisely(g As Graphics, _
        text As String, font As Font, proposedSize As Size, _
        stringFormat As StringFormat) As Rectangle
    '解像度を引き継いで、Bitmapを作成する
    Dim bmp As New Bitmap(proposedSize.Width, proposedSize.Height, g)
    'BitmapのGraphicsを作成する
    Dim bmpGraphics As Graphics = Graphics.FromImage(bmp)
    'Graphicsのプロパティを引き継ぐ
    bmpGraphics.TextRenderingHint = g.TextRenderingHint
    bmpGraphics.TextContrast = g.TextContrast
    bmpGraphics.PixelOffsetMode = g.PixelOffsetMode
    '文字列の描かれていない部分の色を取得する
    Dim backColor As Color = bmp.GetPixel(0, 0)
    '実際にBitmapに文字列を描画する
    bmpGraphics.DrawString(text, font, Brushes.Black, _
        New RectangleF(0.0F, 0.0F, proposedSize.Width, proposedSize.Height), _
        stringFormat)
    bmpGraphics.Dispose()
    '文字列が描画されている範囲を計測する
    Dim resultRect As Rectangle = MeasureForegroundArea(bmp, backColor)
    bmp.Dispose()

    Return resultRect
End Function

''' <summary>
''' 指定されたBitmapで、backColor以外の色が使われている範囲を計測する
''' </summary>
Private Shared Function MeasureForegroundArea(bmp As Bitmap, _
        backColor As Color) As Rectangle
    Dim backColorArgb As Integer = backColor.ToArgb()
    Dim maxWidth As Integer = bmp.Width
    Dim maxHeight As Integer = bmp.Height

    Dim x As Integer
    Dim y As Integer

    '左側の空白部分を計測する
    Dim leftPosition As Integer = -1
    For x = 0 To maxWidth - 1
        For y = 0 To maxHeight - 1
            '違う色を見つけたときは、位置を決定する
            If bmp.GetPixel(x, y).ToArgb() <> backColorArgb Then
                leftPosition = x
                Exit For
            End If
        Next
        If 0 <= leftPosition Then
            Exit For
        End If
    Next
    '違う色が見つからなかった時
    If leftPosition < 0 Then
        Return Rectangle.Empty
    End If

    '右側の空白部分を計測する
    Dim rightPosition As Integer = -1
    For x = maxWidth - 1 To leftPosition + 1 Step -1
        For y = 0 To maxHeight - 1
            If bmp.GetPixel(x, y).ToArgb() <> backColorArgb Then
                rightPosition = x
                Exit For
            End If
        Next
        If 0 <= rightPosition Then
            Exit For
        End If
    Next
    If rightPosition < 0 Then
        rightPosition = leftPosition
    End If

    '上の空白部分を計測する
    Dim topPosition As Integer = -1
    For y = 0 To maxHeight - 1
        For x = leftPosition To rightPosition
            If bmp.GetPixel(x, y).ToArgb() <> backColorArgb Then
                topPosition = y
                Exit For
            End If
        Next
        If 0 <= topPosition Then
            Exit For
        End If
    Next
    If topPosition < 0 Then
        Return Rectangle.Empty
    End If

    '下の空白部分を計測する
    Dim bottomPosition As Integer = -1
    For y = maxHeight - 1 To topPosition + 1 Step -1
        For x = leftPosition To rightPosition
            If bmp.GetPixel(x, y).ToArgb() <> backColorArgb Then
                bottomPosition = y
                Exit For
            End If
        Next
        If 0 <= bottomPosition Then
            Exit For
        End If
        y -= 1
    Next
    If bottomPosition < 0 Then
        bottomPosition = topPosition
    End If

    '結果を返す
    Return New Rectangle(leftPosition, topPosition, _
        rightPosition - leftPosition, bottomPosition - topPosition)
End Function

Private Shared Function MeasureForegroundArea(bmp As Bitmap) As Rectangle
    Return MeasureForegroundArea(bmp, bmp.GetPixel(0, 0))
End Function
C#
コードを隠すコードを選択
//using System.Drawing;

/// <summary>
/// Graphics.DrawStringで文字列を描画した時の大きさと位置を正確に計測する
/// </summary>
/// <param name="g">文字列を描画するGraphics</param>
/// <param name="text">描画する文字列</param>
/// <param name="font">描画に使用するフォント</param>
/// <param name="proposedSize">これ以上大きいことはないというサイズ。
/// できるだけ小さくすること。</param>
/// <param name="stringFormat">描画に使用するStringFormat</param>
/// <returns>文字列が描画される範囲。
/// 見つからなかった時は、Rectangle.Empty。</returns>
public static Rectangle MeasureStringPrecisely(Graphics g,
    string text, Font font, Size proposedSize, StringFormat stringFormat)
{
    //解像度を引き継いで、Bitmapを作成する
    Bitmap bmp = new Bitmap(proposedSize.Width, proposedSize.Height, g);
    //BitmapのGraphicsを作成する
    Graphics bmpGraphics = Graphics.FromImage(bmp);
    //Graphicsのプロパティを引き継ぐ
    bmpGraphics.TextRenderingHint = g.TextRenderingHint;
    bmpGraphics.TextContrast = g.TextContrast;
    bmpGraphics.PixelOffsetMode = g.PixelOffsetMode;
    //文字列の描かれていない部分の色を取得する
    Color backColor = bmp.GetPixel(0, 0);
    //実際にBitmapに文字列を描画する
    bmpGraphics.DrawString(text, font, Brushes.Black,
        new RectangleF(0f, 0f, proposedSize.Width, proposedSize.Height),
        stringFormat);
    bmpGraphics.Dispose();
    //文字列が描画されている範囲を計測する
    Rectangle resultRect = MeasureForegroundArea(bmp, backColor);
    bmp.Dispose();

    return resultRect;
}

/// <summary>
/// 指定されたBitmapで、backColor以外の色が使われている範囲を計測する
/// </summary>
private static Rectangle MeasureForegroundArea(Bitmap bmp, Color backColor)
{
    int backColorArgb = backColor.ToArgb();
    int maxWidth = bmp.Width;
    int maxHeight = bmp.Height;

    //左側の空白部分を計測する
    int leftPosition = -1;
    for (int x = 0; x < maxWidth; x++)
    {
        for (int y = 0; y < maxHeight; y++)
        {
            //違う色を見つけたときは、位置を決定する
            if (bmp.GetPixel(x, y).ToArgb() != backColorArgb)
            {
                leftPosition = x;
                break;
            }
        }
        if (0 <= leftPosition)
        {
            break;
        }
    }
    //違う色が見つからなかった時
    if (leftPosition < 0)
    {
        return Rectangle.Empty;
    }

    //右側の空白部分を計測する
    int rightPosition = -1;
    for (int x = maxWidth - 1; leftPosition < x; x--)
    {
        for (int y = 0; y < maxHeight; y++)
        {
            if (bmp.GetPixel(x, y).ToArgb() != backColorArgb)
            {
                rightPosition = x;
                break;
            }
        }
        if (0 <= rightPosition)
        {
            break;
        }
    }
    if (rightPosition < 0)
    {
        rightPosition = leftPosition;
    }

    //上の空白部分を計測する
    int topPosition = -1;
    for (int y = 0; y < maxHeight; y++)
    {
        for (int x = leftPosition; x <= rightPosition; x++)
        {
            if (bmp.GetPixel(x, y).ToArgb() != backColorArgb)
            {
                topPosition = y;
                break;
            }
        }
        if (0 <= topPosition)
        {
            break;
        }
    }
    if (topPosition < 0)
    {
        return Rectangle.Empty;
    }

    //下の空白部分を計測する
    int bottomPosition = -1;
    for (int y = maxHeight - 1; topPosition < y; y--)
    {
        for (int x = leftPosition; x <= rightPosition; x++)
        {
            if (bmp.GetPixel(x, y).ToArgb() != backColorArgb)
            {
                bottomPosition = y;
                break;
            }
        }
        if (0 <= bottomPosition)
        {
            break;
        }
    }
    if (bottomPosition < 0)
    {
        bottomPosition = topPosition;
    }

    //結果を返す
    return new Rectangle(leftPosition, topPosition,
        rightPosition - leftPosition, bottomPosition - topPosition);
}

private static Rectangle MeasureForegroundArea(Bitmap bmp)
{
    return MeasureForegroundArea(bmp, bmp.GetPixel(0, 0));
}

このメソッドを実際に使用して、MeasureStringメソッドの結果を比較する例を以下に示します。このコードは、まずMeasureStringメソッドが返す大きさで赤い四角を描画し、次に上記のメソッドが返す大きさ(範囲)で青い四角を描画し、最後に文字列を黒で描画しています。結果は、今まで通り、PictureBoxコントロール"PictureBox1"に表示しています。

正確に文字列が描画される範囲を計測した結果

VB.NET
コードを隠すコードを選択
'Imports System.Drawing

'表示する文字列
Dim s As String = "DOBON.NET"

'描画先とするImageオブジェクトを作成する
Dim canvas As New Bitmap(PictureBox1.Width, PictureBox1.Height)
'ImageオブジェクトのGraphicsオブジェクトを作成する
Dim g As Graphics = Graphics.FromImage(canvas)
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias

'フォントオブジェクトの作成
Dim fnt As New Font("Arial", 25, FontStyle.Italic)
'StringFormatオブジェクトの作成
Dim sf As New StringFormat()

'幅の最大値が1000ピクセルとして、文字列を描画するときの大きさを計測する
Dim stringSize As SizeF = g.MeasureString(s, fnt, 1000, sf)
'取得した文字列の大きさを使って四角を描画する
g.DrawRectangle(Pens.Red, 0, 0, stringSize.Width, stringSize.Height)

'より正確に大きさを計測する
Dim rect As Rectangle = MeasureStringPrecisely(g, s, fnt, canvas.Size, sf)
g.DrawRectangle(Pens.Blue, rect.Left, rect.Top, rect.Width, rect.Height)

'文字列を描画する
g.DrawString(s, fnt, Brushes.Black, 0, 0, sf)

'リソースを解放する
fnt.Dispose()
sf.Dispose()
g.Dispose()

'PictureBox1に表示する
PictureBox1.Image = canvas
C#
コードを隠すコードを選択
//using System.Drawing;

//表示する文字列
string s = "DOBON.NET";

//描画先とするImageオブジェクトを作成する
Bitmap canvas = new Bitmap(PictureBox1.Width, PictureBox1.Height);
//ImageオブジェクトのGraphicsオブジェクトを作成する
Graphics g = Graphics.FromImage(canvas);
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;

//フォントオブジェクトの作成
Font fnt = new Font("Arial", 25, FontStyle.Italic);
//StringFormatオブジェクトの作成
StringFormat sf = new StringFormat();

//幅の最大値が1000ピクセルとして、文字列を描画するときの大きさを計測する
SizeF stringSize = g.MeasureString(s, fnt, 1000, sf);
//取得した文字列の大きさを使って四角を描画する
g.DrawRectangle(Pens.Red, 0, 0, stringSize.Width, stringSize.Height);

//より正確に大きさを計測する
Rectangle rect = MeasureStringPrecisely(g, s, fnt, canvas.Size, sf);
g.DrawRectangle(Pens.Blue, rect.Left, rect.Top, rect.Width, rect.Height);

//文字列を描画する
g.DrawString(s, fnt, Brushes.Black, 0, 0, sf);

//リソースを解放する
fnt.Dispose();
sf.Dispose();
g.Dispose();

//PictureBox1に表示する
PictureBox1.Image = canvas;
  • 履歴:
  • 2007/2/14 TextRenderer.MeasureTextメソッドを使用する方法を追加。
  • 2012/8/4 表示する方法を、PictureBox.Imageプロパティを使った方法に変更。
  • 2014/6/30 「実際に描画して計測する方法」を追加。画像を.NET Framework 4.5での結果に更新。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • このサイトで紹介されているコードの多くは、例外処理が省略されています。例外処理については、こちらをご覧ください。
  • コードの先頭に記述されている「Imports ??? がソースファイルの一番上に書かれているものとする」(C#では、「using ???; がソースファイルの一番上に書かれているものとする」)の意味が分からないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。