第 18 章 GDI+

GDI(Graphics Device Interface)+は、文字列とグラフィックスの出力を生成し、ビットマップやその他の種類のイメージを処理する.NET Framework技術です。前の章で既にGDI+メソッドを使用しましたが、この新しいアーキテクチャの重要な部分を完全には説明しませんでした。GDI+は大きく3つの部分に分けることができます。

  • 2次元ベクタグラフィックス このサブセットには、色で塗りつぶされる線、多角形、楕円、円弧が含まれる。GDI+は、グラデーションブラシ(第17章の「17.3.4 独自のコントロールの作成」でも使用した)、複雑なカーディナルスプライン曲線やベジエスプライン曲線、スケーラブルな領域、パスの平坦化など、多くの高度な機能をサポートする。2次元ベクタグラフィックスを実行するオブジェクトのほとんどは、System.Drawing名前空間とSystem.Drawing.Drawing2D名前空間にある。
  • イメージング このサブセットは、ビットマップやアイコンなどのイメージを表示、処理するオブジェクトを含んでいる。ラスタイメージを操作するための.NETの機能は非常に優れており、イメージの拡大、一般的なグラフィックス形式との変換、アルファブレンドを使用した半透明領域もサポートしている。イメージの作成に使用するオブジェクトは、System.Drawing.Imaging名前空間にある。
  • タイポグラフィ このサブセットは、文字列をさまざまな形式、色、スタイルで表示するために使用するオブジェクトが含まれている。文字列の表示方法を完全に制御し、さらにアンチエイリアシングや、PDAなどのLCDディスプレイで文字列を滑らかに描画する方法もある。タイポグラフィのオブジェクトはSystem.Drawing.Text名前空間にある。

18.1 | 2次元ベクタグラフィックス

GDI+の3つの区分には共通するオブジェクトが数多くあり、それらは似たようなプログラミングモデルを使用します。この節では、2次元ベクタグラフィックスについて説明しますが、大部分の概念はGDI+の他の部分にも同じように適用されます。

注

 この章のサンプルプログラムはすべて、できる限り簡潔にするために、ソースファイルの先頭に次のImportsステートメントが存在することを想定しています。

Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Drawing.Imaging
Imports System.Drawing.Text

18.1.1| Graphicsオブジェクト

グラフィックス要素を描画するために真っ先に実行しなければならない操作は、Graphicsオブジェクトの参照を取得することです。このオブジェクトは、たとえ目に見える描画面でなくても、線やその他の形状を描画するキャンバスを表します(Graphicsオブジェクトは、標準的なGDIプログラミングのデバイスコンテキストにほぼ対応しています)。Graphicsオブジェクトにはパブリックのコンストラクタメソッドがないので、Newキーワードを使用して作成することはできません。その代わりに、イベントの引数からGraphicsオブジェクトを取得するか、またはFormオブジェクトとすべてのコントロールオブジェクトにあるCreateGraphicsメソッドを使用してGraphicsオブジェクトを取得できます。

次のプログラムは、イベントの引数からGraphicsオブジェクトを取得します。フォームのPaintイベントで、フォームと同じ大きさの円を表示します。このイベントに渡されるPaintEventArgsオブジェクトのプロパティには、再描画されるオブジェクトの描画面を表すGraphicsオブジェクトがあります。

                  
Private Sub Form1_Paint(ByVal sender As Object, _
    ByVal e As PaintEventArgs) Handles MyBase.Paint
    ' フォームの描画面に対応するGraphicsオブジェクトを取得します。
    Dim gr As Graphics = e.Graphics
    ' 背景を薄灰色でクリアします。
    gr.Clear(Color.Azure)
    ' フォームと同じ大きさの赤い楕円を描画します。
    gr.DrawEllipse(Pens.Red, 0, 0, Me.ClientSize.Width, Me.ClientSize.Height)
End Sub

たとえば、オーナー描画コントロールに使用するDrawItemイベントなど、その他のイベントでもこのような方法でGraphicsオブジェクトの参照を取得することができます。しかし、このオブジェクトを使用できないイベントの場合には、フォームやコントロールのCreateGraphicsメソッドを使用してGraphicsオブジェクトを取得する必要があります。イベントを使用する場合との主な違いは、プロシージャを終了する前にGraphicsオブジェクトを破棄する必要があることです。そうしなかった場合には、対応するWindowsリソース(つまり、基本となるデバイスコンテキスト)は、次のガベージコレクションでしか破棄されません。次の例は、フォームがサイズ変更されたときに自動的に再描画される、フォームと同じ大きさの楕円の描画方法です。

                  
Private Sub Form1_Resize(ByVal sender As Object, _
    ByVal e As EventArgs) Handles MyBase.Resize
    ' フォームの描画面に対応するGraphicsオブジェクトを取得します。
    Dim gr As Graphics = Me.CreateGraphics
    ' 背景を薄灰色でクリアします。
    gr.Clear(Color.Azure)
    ' フォームと同じ大きさの赤い楕円を描画します。
    gr.DrawEllipse(Pens.Red, 0, 0, Me.ClientSize.Width, Me.ClientSize.Height)
    ' Graphicsオブジェクトを破棄します。
    gr.Dispose()
End Sub

Graphicsオブジェクトには多数のプロパティとメソッドがあります。以降の節でそれらの大部分について説明します。

18.1.2| 線、四角形、多角形、楕円、円弧

Graphicsオブジェクトには線、四角形、楕円などのグラフィックス要素を描画するためのメソッドがあります。各メソッドはオーバーロードされるので、複数の構文を使用できますが、最初の引数でPenオブジェクトを指定し、残りの引数で座標セットや境界となる四角形を指定します。Visual Basic 6と異なる点は、一般的に用いられている前景色の概念がないことと、各呼び出しでPenオブジェクトを指定する必要があることです。さらに、Graphicsオブジェクトは最後の描画ポイントを記憶していません。Graphicsオブジェクトは状態に関する情報を持ちません。しかし、後で説明するように、このオブジェクトは、プロパティに割り当てることができるいくつかの重要な値を保持します。

                  
' Graphicsオブジェクトを作成します。
Dim gr As Graphics = Me.CreateGraphics
' (100, 30)から(500, 300)まで赤い線を描画します。
gr.DrawLine(Pens.Red, 100, 30, 500, 300)
' (130, 50)を左上隅として1辺が300ピクセルの青い正方形を描画します。
gr.DrawRectangle(Pens.Blue, New Rectangle(130, 50, 300, 300))
' 中心が(350,250)で半径が200の緑の円を描画します。
Dim r As Integer = 200
gr.DrawEllipse(Pens.Green, 350 - r, 250 - r, r * 2, r * 2)
' Graphicsオブジェクトを破棄します。
gr.Dispose()

DrawArcメソッドを使用した楕円の円弧の描画は、楕円の描画と似ていますが、引数をさらにもう2つ提供する必要があります。それらは開始角度とスイープ角度で、これらは両方とも度数で計測されます。開始角度はX座標から時計回りに測定され、スイープ角度もまた時計回りに測定されます。たとえば次のステートメントがあります。

                  
' 中心が(200, 150)で半径が100の円の4分の1を描画します。
Dim r2 As Integer = 100
gr.DrawArc(Pens.Blue, 200 - r2, 150 - r2, r2 * 2, r2 * 2, 0, 90)

これは、次の円弧を描画します。

図18-1 円弧の描画

▲図18-1 円弧の描画

いくつかのメソッドでは、1回の実行で複数の線を作成することができます。DrawLinesメソッドは一連の接続された線を描画します。このメソッドはPoint構造体またはPointF構造体の配列をとります(2つの構造体の違いは、PointFが2つの座標をSingle値として表すことだけです)。

                  
' 3つの直線を描画します。
Dim points() As Point = {New Point(10, 10), _
    New Point(100, 80), New Point(200, 20), New Point(300, 100)}
gr.DrawLines(Pens.Black, points)

最後の線の終了点が最初の線の開始点と一致する場合には、多角形を描画していることになるので、DrawPolygonメソッドを使用することができます。

                  
' ひし形を描画します。
' 実行中にPoint構造体の配列を構築しています。
gr.DrawPolygon(Pens.Red, New Point() {New Point(200, 50), _
    New Point(300, 100), New Point(200, 150), New Point(100, 100)})
    

また、DrawRectanglesメソッドを使用して、複数の四角形をすばやく描画することができます。

                  
' 3つの四角形を描画します。
Dim rects() As Rectangle = {New Rectangle(50, 30, 200, 100), _
    New Rectangle(70, 40, 220, 110), New Rectangle(90, 50, 240, 120)}
gr.DrawRectangles(Pens.Green, rects)

18.1.3| カーディナルスプラインとベジエスプライン

GDI+は、円や楕円の弧として表現できない複雑な曲線の形式を2種類サポートしています。それらは「カーディナルスプライン」と「ベジエスプライン」です。カーディナルスプラインは、鉄や木の細片など曲げられる素材の一片を使用して作成する曲線で、それがX-Y平面上の指定された固定ポイントを通ります。使用している素材が(ロープやゴムひものような)無限に曲げられるものでない限り、素材によって描画されるパスは、その連結ポイントに鋭角を作らない曲線になります。使用される素材の屈曲性(または「テンション」と呼ばれます)の度合いに応じて、指定されたポイントは、異なる曲線を生成します。既定のテンションは0.5です。次のプログラムは、テンションが0(これは無限の屈曲性を持つ素材に対応するので、直線を描画します)から始まって2で終わる5つのカーディナルスプラインを描画します。

                  
Dim points() As Point = {New Point(100, 100), New Point(200, 200), _
    New Point(250, 30), New Point(350, 100)}
Dim tension As Single
For tension = 0 To 2 Step 0.5
    gr.DrawCurve(Pens.Blue, points, tension)
Next

図18-2は結果のカーディナルスプラインを示しています。DrawClosedCurveメソッドを使用すれば、閉じたカーディナルスプラインを描画することもできます。このメソッドはPoint構造体の配列をとります。

図18-2 テンションが0(直線)から2までの0.5刻み5つのカーディナルスプライン

▲図18-2 テンションが0(直線)から2までの0.5刻み5つのカーディナルスプライン

ベジエスプラインは、開始点、終了点、2つの制御点の4つのポイントで指定される曲線です。曲線は制御点を通りませんが、制御点の方向に引かれます。より正確に言えば、ベジエスプラインは、開始点と最初の制御点を結ぶ架空の線によって示される方向で始まり、2番目の制御点と終了点を結ぶ架空の線で示される方向で終わります。図18-3は、2つの制御点への接線も示したベジエ曲線の例です。DrawBezierメソッドを使用すれば、この種類の曲線を描画することができます。このメソッドは、開始点、最初の制御点、2番目の制御点、終了点の4つのPoint構造体を(この順番で)とります。たとえば、次は、図18-3のベジエ曲線だけを描画するプログラムです。

                  
gr.DrawBezier(Pens.Black, New Point(10, 30), New Point(100, 20), _
    New Point(140, 190), New Point(200, 200))

DrawBeziersメソッドでは、接続された複数のベジエ曲線を描画することもできます。DrawBeziersメソッドはPoint構造体の配列をとります。最初の4つのPoint構造体は最初の曲線を定義し、後に続く3つのPoint構造体は、それぞれ次の曲線の2つの制御点と終了点を定義します。

図18-3 両端と制御点を結ぶ2つの接線があるベジエ曲線

▲図18-3 両端と制御点を結ぶ2つの接線があるベジエ曲線

18.1.4| Penオブジェクト

これまでに説明してきた例ではすべて、Pens.Blackなど、Pensクラスにある定義済みオブジェクトを使用しました。SystemPensクラスを使用して、システムカラーに対応するPenオブジェクトのいずれかを使用することもできます。たとえば、アクティブウィンドウのタイトルバーに定義されている色を使用するSystemPens.ActiveCaptionTextなどです。しかし、PensクラスやSystemPensクラスにはない色を使用したり、1ピクセルより広い幅を定義したり、破線を定義したり、描画される線や曲線の外観をさらに制御したい場合には、独自のカスタムPenオブジェクトを作成する必要があります。定義済みのPenオブジェクトとカスタムPenオブジェクトとの重要な違いとして、カスタムPenオブジェクトは常にDisposeメソッドを呼び出して破棄する必要があります。次は、2つの例です。

                  
' カスタムカラーのペンで内側の四角形を描画します。
Dim p1 As New Pen(Color.FromArgb(128, 0, 60))
gr.DrawRectangle(p1, 50, 50, 100, 50)
' 幅4ピクセルの青いペンで外側の四角形を描画します。
Dim p2 As New Pen(Color.Blue, 4)
gr.DrawRectangle(p2, 10, 10, 200, 100)
' Penオブジェクトを破棄します。
p1.Dispose()
p2.Dispose()

1ピクセルよりも幅の広いPenオブジェクトを操作するときには、その配置を考慮する必要もあります。配置は、ペン幅の中でどのように描画されるかを指定します。たとえば、図18-4は同じ長さの辺を持つ2つの正方形を示していますが、それぞれ異なるペン配置で描画されています。

                  
Dim p As New Pen(Color.Yellow, 12)
Dim rect As New Rectangle(20, 20, 100, 100)
' 最初の正方形を中央揃え(既定)で描画します。
gr.DrawRectangle(p, rect)
' 参照として理論上の線も描画します。
gr.DrawRectangle(Pens.Black, rect)

' 四角形を右側に移動させます。
rect.Offset(150, 0)
' 配置を内側に変更して、2つの正方形をもう一度描画します。
p.Alignment = Drawing.Drawing2D.PenAlignment.Inset
gr.DrawRectangle(p, rect)
gr.DrawRectangle(Pens.Black, rect)
' カスタムPenオブジェクトを破棄します。
p.Dispose()

PenAlignment列挙体では、さらにLeft、Outset、Rightの3つの設定が定義されています。ただし、このプログラムでは、既定の設定(Center)と同じ結果になります。

図18-4 それぞれ異なるペン配置で描画された2つの正方形

▲図18-4 それぞれ異なるペン配置で描画された2つの正方形

Penオブジェクトのその他のプロパティを設定して、カスタムの線を作成することができます。たとえば、DashStyleプロパティを使用して定義済みのパターンを利用した破線を描画したり、Single値の配列をDashPatternプロパティに割り当てて、カスタムの破線パターンを作成することもできます。StartCapプロパティとEndCapプロパティは列挙値をとり、これらを使用して線の開始点と終了点の形(キャップ)を変更できるので、簡単に矢印やその他の形を作成することができます。図18-5は、3つのプロパティを使用して得られる数多くのさまざまな線形のうちのいくつかを示しています。次はそれらの出力を作成するプログラムです。

                  
Dim p1 As New Pen(Color.Black, 3)
p1.DashStyle = Drawing.Drawing2D.DashStyle.Dash
gr.DrawLine(p1, 10, 10, 200, 10)
p1.DashStyle = Drawing.Drawing2D.DashStyle.DashDot
gr.DrawLine(p1, 10, 30, 200, 30)
p1.DashStyle = Drawing.Drawing2D.DashStyle.DashDotDot
gr.DrawLine(p1, 10, 50, 200, 50)
p1.DashStyle = Drawing.Drawing2D.DashStyle.Dot
gr.DrawLine(p1, 10, 70, 200, 70)

' カスタムの破線パターンを作成します。
Dim sngArray() As Single = {4, 4, 8, 4, 12, 4}
p1.DashPattern = sngArray
gr.DrawLine(p1, 10, 90, 200, 90)

' 既定以外のキャップを持つペンを表示します。
Dim p2 As New Pen(Color.Black, 8)
p2.StartCap = Drawing.Drawing2D.LineCap.DiamondAnchor
p2.EndCap = Drawing.Drawing2D.LineCap.ArrowAnchor
gr.DrawLine(p2, 280, 30, 500, 30)

p2.StartCap = Drawing.Drawing2D.LineCap.RoundAnchor
p2.EndCap = Drawing.Drawing2D.LineCap.Round
gr.DrawLine(p2, 280, 70, 500, 70)

' カスタムのPenオブジェクトを破棄します。
p1.Dispose()
p2.Dispose()

図18-5 破線および開始点と終了点のキャップが異なる線

▲図18-5 破線および開始点と終了点のキャップが異なる線

18.1.5| パス

GraphicsPathオブジェクト(パス)は、単一のエンティティとして描画され操作されるグラフィックス要素の集まりです。パスは、線、円弧、四角形、楕円、多角形およびこれまで説明してきた一般的な形状のすべてを含むことができます。また、必要があれば、パスは1つまたは複数の閉じた図形を含むこともできます。GraphicsPathオブジェクトを描画する前に、1つまたは複数のAddxxxxメソッドを使用してグラフィックスを作成し、その内容を定義する必要があります。たとえ別個の線をパスに追加しても、2つのAddLineメソッドの間で明示的にStartFigureメソッドを実行しない限り、各区分の終了点は自動的に次の区分の開始点と接続されます。次は、平行六面体と三角形を含むパスの例です。

                  
' 新しいGraphicsPathオブジェクトを作成します。
Dim pa As New GraphicsPath()
' 図形を開始します。
pa.StartFigure()
pa.AddRectangle(New Rectangle(20, 20, 200, 150))
pa.AddRectangle(New Rectangle(50, 50, 200, 150))
pa.AddLine(20, 20, 50, 50)
pa.StartFigure() ' 2つの線がつながらないようにします。
pa.AddLine(220, 20, 250, 50)
pa.StartFigure() ' 2つの線がつながらないようにします。
pa.AddLine(220, 170, 250, 200)
pa.StartFigure() ' 2つの線がつながらないようにします。
pa.AddLine(20, 170, 50, 200)

' もう1つの三角の(閉じた)図形を開始します。
pa.StartFigure()
pa.AddLine(300, 20, 400, 20)
pa.AddLine(400, 20, 400, 120)
pa.CloseFigure()

CloseFigureメソッドは自動的に最も新しい開いた多角形を閉じます。パスを定義したらDrawPathメソッドを使用して、選択したペンで描画できます。

                  
' ...(前述のコードの続き)...
' 太い赤のペンでパスを描画します。
Dim p As New Pen(Color.Red, 3)
gr.DrawPath(p, pa)
' GraphicsPathオブジェクトとPenオブジェクトの両方を破棄します。
pa.Dispose()
p.Dispose()

図18-6は、このプログラムの効果を示しています。

図18-6 2つの図を含むGraphicsPathオブジェクト

▲図18-6 2つの図を含むGraphicsPathオブジェクト

18.1.6| 図形の塗りつぶし

Graphicsオブジェクトには塗りつぶした幾何学的図形を作成するメソッドが8つあります。それらは、FillRectangleFillRectanglesFillEllipseFillPolygonFillPieFillClosedCurveFillPathFillRegionです。これらのメソッドはいくつかのオーバーロードをサポートしますが、それらはすべて最初の引数として、形状の塗りつぶし方を決定するBrushオブジェクトをとります。Brushクラスには、SolidBrushHatchBrushLinearGradientBrushTextureBrushなど、複数のブラシがあり、それらはすべてSystem.Drawing.Brushクラスから派生するので、一般的なブラシが想定されている場合には常にどれでも使用することができます。ブラシの種類については次の節で説明するので、ここでは定義済みの単色のブラシのみを使用します。

                  
' 緑で塗りつぶした四角形を描画します。
gr.FillRectangle(Brushes.Green, New Rectangle(20, 10, 200, 100))
' 青で塗りつぶした楕円を描画します。
gr.FillEllipse(Brushes.Blue, 20, 150, 200, 100)
' 赤い扇形(楕円の一部)を描画します。
gr.FillPie(Brushes.Red, 320, 150, 200, 100, -45, 90)
' 楕円の残りをピンクで描画します。
gr.FillPie(Brushes.Pink, 320, 150, 200, 100, 45, 270)
' システムブラシは破棄する必要がありません。

FillPolygonメソッドを使用すれば、変則的な多角形を塗りつぶすことができます。このメソッドは、多角形の各頂点を定義するPoint構造体の配列をとります。また、引数ではさらに塗りつぶしモードを指定することができ、交互モード(Alternate、既定)と全域モード(Winding)のどちらかを選択することができます。図18-7は、同じ多角形での2つのモードの効果を示しています。この図は次のプログラムで作成されます。

                  
Dim points() As Point = {New Point(200, 100), New Point(300, 300), _
    New Point(50, 170), New Point(350, 170), New Point(100, 300)}
gr.FillPolygon(Brushes.Gray, points, Drawing.Drawing2D.FillMode.Alternate)
Dim points2() As Point = {New Point(600, 100), New Point(700, 300), _
    New Point(450, 170), New Point(750, 170), New Point(500, 300)}
gr.FillPolygon(Brushes.GreenYellow, points2, Drawing.Drawing2D.FillMode.Winding)

FillClosedCurveメソッドを使用すれば、変則的な曲線を塗りつぶすことができます。このメソッドには、Point構造体の配列、テンション値、塗りつぶしモードの引数などがあります。FillPathメソッドを使用すれば、複雑なパスを塗りつぶすことができ、FillRegionメソッドを使用すれば、Regionオブジェクトを塗りつぶすことができます(この章の後の方でRegionオブジェクトについて説明します)。

図18-7 左側は交互モード、右側は全域モードで塗りつぶした星形

▲図18-7 左側は交互モード、右側は全域モードで塗りつぶした星形

18.1.7| Brushオブジェクト

これまでに説明してきた例はすべて、BrushesクラスやSystemBrushesクラスのプロパティとして定義済みのBrushオブジェクトを使用しています。GDI+にある多数のクラスを使用すれば、カスタムブラシを作成することができます。カスタムブラシと定義済みブラシの違いとして、カスタムブラシはNothingに設定するかまたはスコープから外す前に、破棄する必要があります。最も簡単なカスタムブラシは、単色のSolidBrushオブジェクトで、そのコンストラクタには色を指定します。

                  
' カスタムカラーでSolidBrushオブジェクトを作成し、四角形を塗りつぶします。
Dim br As New SolidBrush(Color.FromArgb(128, 30, 100))
gr.FillRectangle(br, New Rectangle(10, 10, 200, 100))
' ブラシを破棄します。
br.Dispose()

System.Drawing.Drawing2D名前空間のHatchBrushクラスは、56種類の定義済みスタイルのいずれかを使用する2色ブラシです。次のプログラムは、図18-8の左上の四角形を生成します。

                  
Dim br1 As New HatchBrush(HatchStyle.BackwardDiagonal, _
    Color.White, Color.Blue)
gr.FillRectangle(br1, New Rectangle(10, 10, 200, 100))
br1.Dispose()

図18-8 ハッチブラシで使用できる56のスタイルのうちの6個

▲図18-8 ハッチブラシで使用できる56のスタイルのうちの6個

第17章の「17.3.4 独自のコントロールの作成」では、LinearGradientBrushクラスを使用して線形グラデーション効果を実現しました。このクラスには学習すべきその他多くの点があります。グラデーションブラシは、典型的な背景画面やMicrosoft PowerPointのスライドのように、開始色から終了色まで微妙に変化する色をすべて含んでいます。線形グラデーションブラシを作成する最も簡単な方法は、LinearGradientBrushオブジェクトのコンストラクタに、サイズ(Rectangle構造体)、2つの色、方向を指定することです。その後で、このブラシを使用して塗りつぶされた図形を描画することができます。

                  
Dim br As New LinearGradientBrush(New Rectangle(0, 0, 200, 100), _
    Color.Blue, Color.Black, LinearGradientMode.ForwardDiagonal)
gr.FillRectangle(br, 0, 0, 200, 100)
gr.FillRectangle(br, 220, 0, 200, 100)
br.Dispose()

このプログラムのコンストラクタでは、水平方向、垂直方向、正方向の対角線、逆方向の対角線のグラデーションの4つの方向を指示できます。塗りつぶされる形状がブラシのサイズよりも大きい場合には、その形状を覆うようにブラシが並べて表示されます。塗りつぶされる形状の座標がブラシのサイズの完全な倍数ではない場合、図18-9の右側にある四角形の場合のように、形状の左上隅の色がブラシの最初の色と一致しません。WrapModeプロパティを使用すれば、形状を塗りつぶすためのブラシの使用法を指定できます。このプロパティでは、Tile(既定)、TileFlipX、TileFlipY、TileFlipXY、Clampを指定できます。

図18-9 線形グラデーションブラシで塗りつぶされた2つの四角形

▲図18-9 線形グラデーションブラシで塗りつぶされた2つの四角形

LinearGradientBrushオブジェクトは、既定で線形のグラデーションを行い、開始色から終了色へと線形が色が変化します。しかし、Blendプロパティを使用すれば、他のパターンのグラデーションを定義することができます(グラデーションブラシについて詳しくは、.NET Framework SDKドキュメントを参照してください)。

もう1つのブラシとしてTextureBrushクラスがあります。TextureBrushクラスは、コンストラクタでImageオブジェクトと、WrapMode列挙体を指定します。WrapMode列挙体は、塗りつぶされる形状がイメージよりも大きい場合のイメージの並べ方を表します。既定では、イメージは並べて表示されますが、次のコードのように、水平軸、垂直軸、または両方で反転させることもできます(図18-10を参照)。

                  
' ここでは、PictureBoxコントロールに読み込まれているビットマップを使用します。
Dim br1 As New TextureBrush(PictureBox1.Image)
gr.FillRectangle(br1, New Rectangle(20, 20, 250, 150))
br1.Dispose()

Dim br2 As New TextureBrush(PictureBox1.Image, WrapMode.TileFlipY)
gr.FillRectangle(br2, New Rectangle(300, 20, 250, 150))
br2.Dispose()

Dim br3 As New TextureBrush(PictureBox1.Image, WrapMode.TileFlipX)
gr.FillRectangle(br3, New Rectangle(20, 220, 250, 150))
br3.Dispose()

Dim br4 As New TextureBrush(PictureBox1.Image, WrapMode.TileFlipXY)
gr.FillRectangle(br4, New Rectangle(300, 220, 250, 150))
br4.Dispose()

図18-10 ビットマップを並べて表示し、1つの軸または両方の軸で反転させるテクスチャブラシ

▲図18-10 ビットマップを並べて表示し、1つの軸または両方の軸で反転させるテクスチャブラシ

最後のBrushクラスはPathGradientBrushです。これはGraphicsPathオブジェクトを基にしてグラデーションブラシを作成します。あらゆるサイズと形状を覆うように並べて表示される他のブラシとは異なり、PathGradientBrushオブジェクトは、定義に使用されるパスオブジェクトの境界線を越えることはありません。したがって、特定のパスの表面を塗りつぶすために使用できます。PathGradientBrushオブジェクトは、GraphicsPathオブジェクト、中心の色、パスの境界の色などによって定義されます。

                  
' 楕円のパスを定義します。
Dim pa As New GraphicsPath()
pa.AddEllipse(10, 10, 200, 100)
' パスを基にした形状のブラシを作成します。
Dim br As New PathGradientBrush(pa)
br.CenterColor = Color.Yellow
' 要素の色の配列を定義します。
Dim colors() As Color = {Color.Blue}
br.SurroundColors = colors
' パスを塗りつぶします。
gr.FillPath(br, pa)
' ブラシオブジェクトとパスオブジェクトを破棄します。
br.Dispose()
pa.Dispose()

図18-11の楕円は、前述のプログラムの結果を示しています。

図18-11 パスグラデーションブラシの2つの例

▲図18-11 パスグラデーションブラシの2つの例

パスの境界線上に複数の色を指定して、より凝ったパスグラデーションを作成することができます。そのためには、Point配列を使用してパスの頂点を定義し、次に要素の数と等しいColor配列を定義して、ブラシのSurroundColorsプロパティにその配列を渡します。

                  
Dim pa2 As New GraphicsPath()
' 正方形のパスを定義します。
Dim points() As Point = {New Point(300, 10), New Point(500, 10), _
    New Point(500, 210), New Point(300, 210)}
pa2.AddLines(points)
pa2.CloseFigure()
' そのパスを基にしてブラシを作成します。
Dim br2 As New PathGradientBrush(pa2)
' 中心の色と位置を定義します。
br2.CenterColor = Color.Yellow
br2.CenterPoint = New PointF(450, 60)
' それぞれの頂点に対して1つずつ色の配列を定義します。
Dim colors2() As Color = {Color.Blue, Color.Green, Color.Red, Color.Black}
br2.SurroundColors = colors2
' パスを塗りつぶします。
gr.FillPath(br2, pa2)
' ブラシオブジェクトとパスオブジェクトを破棄します。
br2.Dispose()
pa2.Dispose()

図18-11の右側の四角形でこのプログラムの効果を確かめることができます。パスグラデーションブラシは他にも多くの方法でカスタマイズできます。たとえば、色が開始色から中間色へ、中間色から終了色へと変化するように、中間色を追加したり、開始点と終了点の間に独自のパターンのグラデーションを指定できます(詳しくは、.NET Framework SDKドキュメントを参照してください)。

18.1.8| Regionオブジェクト

Regionオブジェクトは、四角形やパスなど、簡単な形状の組み合わせで構成される領域です。次のような多数のメソッドを使用して、これらの形状を組み合わせることができます。

  • Union 形状が現在の領域に追加される(論理和になる)。結果の領域は、元の領域に属するすべての位置と、指定された形状に属するすべての位置を含む。
  • Intersect 形状とその領域が交差する(論理積になる)。結果の領域は、元の領域と指定された形状の両方に共通する位置のみを含む。
  • Exclude 形状はその領域から取り除かれる。結果の領域は元の領域にあって指定された形状にはない位置のみを含む。
  • Xor 結果の領域は、元の領域か指定された形状のどちらかに属するすべての位置を含むが、両方に属する位置は含まない。
  • Complement 結果の領域は、元の領域には属さない指定された領域の位置のみを含む。

さらに、MakeEmptyMakeInfiniteメソッドも、領域のサイズに影響を与えます。MakeEmptyメソッドは領域を初期化して空の領域にし、MakeInfiniteメソッドは領域を初期化して無限のX-Y平面にします。Regionオブジェクトを定義したら、定義済みのブラシやカスタムブラシを使用して、GraphicsオブジェクトのFillRegionメソッドで塗りつぶすことができます。

                  
' 正方形の領域を作成します。
Dim reg As New Region(New Rectangle(20, 20, 300, 300))
' その中に円の穴を作成します。
Dim pa As New GraphicsPath()
pa.AddEllipse(120, 120, 100, 100)
reg.Exclude(pa)
' 別の小さい正方形を中心に追加します。
reg.Union(New Rectangle(150, 150, 40, 40))
' 領域を塗りつぶします。
gr.FillRegion(Brushes.Green, reg)
' パスオブジェクトとRegionオブジェクトを破棄します。
pa.Dispose()
reg.Dispose()

領域はヒットテストとクリッピング(切り取り)に最も役立ちます。ヒットテストは、ある位置や形状が領域に含まれているかどうかを判定するものです。これを利用すれば、マウスポインタが指定された形状の上にあるかどうかを検査することもできます。他にも、さまざまな利用法があります。このようなテストには、IsVisibleメソッドを使用します。IsVisibleメソッドはオーバーロードし、Point構造体、X-Y座標、またはRectangle構造体を引数としてとります。

                  
' 変数regは、Regionオブジェクトを参照していると想定しています。
If reg.IsVisible(Me.MousePosition) Then
    ' マウスポインタは領域の上にあります。
    ・
    ・
    ・
End If

Graphicsオブジェクトにクリッピング領域を定義すれば、領域の外側を描画しないことができます。クリッピング領域を定義するには、GraphicsオブジェクトのSetClipメソッドを使用します。このメソッドは第1引数としてRectangle構造体、GraphicsPathオブジェクト、またはRegionオブジェクトをとり、第2引数にはCombineMode列挙体を指定します。 CombineMode列挙体では、既存の領域を現在のクリッピング領域と置き換えるか、現在のクリッピング領域を追加するか、結合するか、既存の領域と交差させるかを指定できます。次のプログラムは、クリッピング領域として指定されたRegionオブジェクトを使用して、ランダムな色を持つ一連の円を描画します(図18-12でその結果を確認することができます)。

                  
' Regionオブジェクトをクリッピング領域として使用します(現在の領域を置き換えます)。
gr.SetClip(reg, CombineMode.Replace)
' ランダムな色で円を表示します。
Dim x As Integer, y As Integer, r As New Random()
For x = 10 To 400 Step 40
    For y = 10 To 400 Step 40
        Dim br As New SolidBrush( _
            Color.FromArgb(r.Next(0, 255), r.Next(0, 255), r.Next(0, 255)))
        gr.FillEllipse(br, x, y, 30, 30)
        br.Dispose()
    Next
Next

図18-12 クリッピング領域の使用

▲図18-12 クリッピング領域の使用

GraphicsオブジェクトにはVisibleClipBoundsプロパティがあり、これは対応するウィンドウの表示可能領域を表すRectangleF構造体を取得します。このプロパティを使用すれば、領域外にある形状は一切処理しないことにして、グラフィックスルーチンを最適化することができます。プリンタで使用された場合には、このプロパティはページの印刷可能領域を取得します。

18.1.9| アルファブレンド

GDI+の優れた機能の1つに「アルファブレンド」があります。これは半透明色を定義し、それらを使用してすべての種類の形状を描画したり塗りつぶしたりする機能です。Color.FromArgbメソッドの最初の引数で透明度を定義し、255(不透明)から0(完全な透明)までの範囲の数を指定します。値が255より少なければ、描画される図形の後ろに背景が表示されます。たとえば、図18-13は次のプログラムで作成されます。

                  
' 緑色の単色の正方形
gr.FillRectangle(Brushes.Green, 20, 20, 200, 200)
' 半透明の赤い楕円
Dim br1 As New SolidBrush(Color.FromArgb(128, 255, 0, 0))
gr.FillEllipse(br1, 120, 50, 200, 120)
' さらに透明な青い四角形
Dim br2 As New SolidBrush(Color.FromArgb(30, 0, 0, 255))
gr.FillRectangle(br2, 160, 80, 120, 200)
' 半透明の灰色の太い楕円
Dim p As New Pen(Color.FromArgb(128, 128, 128, 128), 5)
gr.DrawEllipse(p, 100, 100, 200, 200)

' ブラシとペンを破棄します。
br1.Dispose()
br2.Dispose()
p.Dispose()

図18-13 アルファブレンドの線と図形

▲図18-13 アルファブレンドの線と図形

18.1.10| 変換

これまでに見てきたプログラム例はすべて、座標の原点がクライアント領域の左上隅にあり、すべての値がピクセルで表されていました。しかし、Graphicsオブジェクトは非常に柔軟性が高く、必要に応じてこれらの設定を変更できます。Graphicsオブジェクト(ビットマップと文字列を含む)を描画するには、次の3つの座標系を考慮しなければなりません。

  • ワールド座標系 グラフィックス環境で使用される座標系で、Graphicsオブジェクトのメソッドに渡す座標はすべてこの座標系で表される。
  • ページ座標系 フォーム、コントロール、または印刷文書を描画する場合に、その描画面で使用される座標系。既定ではこの座標系の原点とスケールはワールド座標系と一致するが、必ずしも一致しなければならないわけではない。たとえば、ワールド座標系の原点をフォームの中心に置くと、数学関数を使用する場合に便利である。ワールド座標とページ座標の間の変換を、「ワールド変換」と呼ぶ。
  • デバイス座標系 描画操作が発生している物理デバイスによって使用される座標系。つまり、画面に描画している場合には実際のウィンドウであり、プリンタに出力を送っている場合にはプリンタ用紙である。システムの計測単位(インチ、ミリメータなど)や水平軸と垂直軸の間の比率などを正確に定義できる。ページ座標とデバイス座標の間の変換を「ページ変換」と呼ぶ。X軸またはY軸に沿った変換は実行できず、また、これらの軸に沿った反転もできない(言い換えれば、ページ変換を使用してイメージを反転させることはできない)。

Graphicsオブジェクトには、ワールド変換の実行方法に影響を与えるメソッドがいくつかあります。TranslateTransformメソッドは、座標系の原点を変更します。X-Y軸の原点を変換すれば、同じGraphicsPathオブジェクトを別の場所に描画することができます。

                  
' 複数回描画できるパスを作成します。
Dim pa As New GraphicsPath()
pa.AddRectangle(New Rectangle(20, 20, 200, 100))
pa.AddEllipse(New Rectangle(30, 30, 180, 80))
' 変換しないでパスを描画します。
gr.DrawPath(Pens.Black, pa)
' 変換を適用して、オブジェクトを別の場所に再描画します。
gr.TranslateTransform(30, 200)
gr.DrawPath(Pens.Red, pa)

図18-14の左側にある2つの図形は、前述のプログラムの効果を示しています。

図18-14 変換、回転、拡大・縮小のワールド変換

▲図18-14 変換、回転、拡大・縮小のワールド変換

RotateTransformメソッドは、Graphicsオブジェクトに描画するすべての形状を指定した角度で回転させることができます。

                  
' 既定値から開始します(変換はしません)。
gr.ResetTransform()
' 変換と回転の両方を適用して、パスを再描画します。
gr.TranslateTransform(400, 0)
gr.RotateTransform(30)
gr.DrawPath(Pens.Green, pa)

図18-14の右上の図形は、このプログラムの結果です。

またScaleTransformメソッドを使用すれば、2つの軸に沿ってスケールを変更することもできます。

                  
gr.ResetTransform()
gr.TranslateTransform(300, 200)
' 角度とスケールを変えた複数の形状を作成します。
Dim i As Integer
Dim scaleX As Single = 1
Dim scaleY As Single = 1
For i = 1 To 6
    gr.ScaleTransform(scaleX, scaleY)
    gr.DrawPath(Pens.Blue, pa)
    scaleX += 0.05
    scaleY += 0.1
Next

図18-14の右下の図形は、このプログラムの結果です。

また、Transformプロパティで複数の変換を組み合わせることもできます。このプロパティは、適用したい変換、回転、スケーリングのすべてを定義するMatrixオブジェクトを使用します。Matrixオブジェクトを正しく初期化するには、スケール変換の背後にある数学理論の知識を必要としますが(詳細については、.NET Framework SDKドキュメントを参照してください)、それほど細かく理解しなくても、Transformプロパティを使用して現在のワールド変換の状態を保存して復元することができます。

                  
' 現在のワールド変換の状態を保存します。
Dim mat As Matrix = gr.Transform
' xxxxTransformメソッドをここで適用します。
・
・
・
' ワールド変換の元の設定を復元します。
gr.Transform = mat

PageUnitプロパティとPageScaleプロパティを使用すれば、ページ変換を制御することができます。PageUnitプロパティは、Pixel(既定)、Inch、Millimeter、Point(1/72インチ)、Display(1/75インチ)、Document(1/300インチ)などを含むGraphicsUnit列挙体になります。

                  
' 単位としてインチを使用します。
gr.PageUnit = GraphicsUnit.Inch

PageScaleプロパティは、指定された係数で画面やプリンタへの出力を拡大、縮小できるようにするSingle値です。

                  
' 出力を元のサイズの10パーセントにします。
gr.PageScale = 0.1

既定では、Penオブジェクトはデバイス座標の単位の幅になるので、ページ変換でピクセル以外の単位を設定する際には、特別な注意が必要です。たとえば、グラフィックス単位としてインチを使用すると、線と曲線は1インチ幅のペンで描画されます。DpiXDpiYの2つの読み取り専用プロパティを使用すれば、Penオブジェクトのコンストラクタに渡す値を取得できるので、この問題に対処することができます。

                  
' 現在のグラフィックス単位に関係なく1ピクセル幅のPenオブジェクトを作成します。
Dim pe As New Pen(Color.Blue, 1 / gr.DpiX)

位置が現在の変換設定でどのような影響を受けるかを、実際に描画せずに確認したい場合もあると思います。これは、GraphicsオブジェクトのTransformPointsメソッドを使用して行うことができます。このメソッドは、どの座標系からどの座標系に変換するのかを指定する2つのCoordinateSpace列挙体と、PointF構造体の配列をとります。

                  
' ワールド座標でのPointF配列を定義します。
Dim points() As PointF = { New PointF(10, 20), New PointF(190, 220)}
' それらをワールド座標からデバイス座標に変換します。
gr.TransformPoints(CoordinateSpace.World, CoordinateSpace.Device, points)
' 結果はPointF配列に入ります。

次は、座標変換に関するその他のヒントです。

  • GraphicsオブジェクトのDrawImageメソッドは、ページ変換ではなくワールド変換に左右される(このメソッドについて詳しくは、「18.2 イメージング」を参照)。
  • ハッチブラシはワールド変換にもページ変換にも左右されない。
  • TextureBrushクラスとLinearGradientBrushクラスには、専用のTranslateプロパティと、TranslateTransformRotateTransformMultiplyTransformResetTransformの各メソッドがある。したがってGraphicsオブジェクトの外観を、他とは関係なく変更できる。

18.2 | イメージング

GDI+では、メタファイルと同じようにラスタイメージも処理できます。イメージを操作するための重要なオブジェクトは、ImageクラスとBitmapクラスです。Imageクラスは、イメージの保存と読み込みを行うためのメソッドを提供し、Bitmapクラスは、Imageクラスから継承して、イメージの個々のピクセルやその他の機能にアクセスできるようにします。これら両方のオブジェクトはSystem.Drawing名前空間にありますが、MetafileColorPaletteなど、System.Drawing.Imaging名前空間のいくつかのオブジェクトも使用します。

18.2.1| イメージの読み込みと保存

ImageクラスとBitmapクラスのLoadFromFileメソッドとLoadFromStreamメソッドでは、ファイルや開いているStreamオブジェクト(ファイルストリームである必要はありません)からイメージを読み込むことができます。次のプログラムは、OpenFileDialogコントロールを使用してユーザーにイメージファイルを問い合わせ、それからDrawImageメソッドを使用して、Graphicsオブジェクトの描画面に表示します。

                  
With OpenFileDialog1
    .Filter = "イメージファイル|*.bmp;*;*.jpg;*.jpeg;*.gif;*.png;*.tif"
    If .ShowDialog = DialogResult.OK Then
        ' ファイルをBitmapオブジェクトに読み込みます。
        Dim bmp As Bitmap = Bitmap.FromFile(.FileName)
        ' Graphicsオブジェクトを作成して、そこにビットマップを描画します。
        Dim gr As Graphics = Me.CreateGraphics
        gr.DrawImage(bmp, 0, 0)
        ' BitmapオブジェクトとGraphicsオブジェクトを破棄します。
        bmp.Dispose()
        gr.Dispose()
   End If
End With

コンストラクタを利用すれば、簡単にイメージをBitmapオブジェクトに読み込むことができます。

                  
'(このステートメントは前述のプログラムのFromFileメソッドに
' 置き換えることができます)。
        Dim bmp As New Bitmap(.Filename)

GDI+は次の形式のイメージを読み込むことができます。BMP(ビットマップ)、GIF(Graphics Interchange Format、イメージを1ピクセルあたり最大8ビットまで圧縮する)、JPEG(Joint Photographic Experts Group、イメージを調整可能な圧縮率で圧縮する)、EXIF(Exchangeable Image File、日付など画像に関する追加情報も格納できる拡張JPEG形式)、PNG(Portable Network Graphics、1ピクセル当たり24ビットまたは48ビットでイメージを格納できるGIFと似た圧縮形式)、TIFF(Tag Image File Format、タグを使用して追加情報を持つ圧縮形式)です。

Saveメソッドを使用すれば、ImageオブジェクトやBitmapオブジェクトに格納されたイメージを保存することができます。このメソッドはファイル名と、ターゲット形式を指定する引数をとります。次のプログラムは、SaveFileDialogコントロールを使用して、複数の形式でのBitmapオブジェクトの内容の保存を、ユーザーに要求します。

                  
' このプログラムは、イメージを保持しているbmpという
' Bitmapオブジェクトの変数があることを想定しています。
With SaveFileDialog1
    .Title = "ターゲットイメージと形式の選択"
    .Filter = "ビットマップ|*.bmp|GIF|*.gif|TIFF|*.tif"
    .OverwritePrompt = True
    If .ShowDialog = DialogResult.OK Then
        Select Case System.IO.Path.GetExtension(.FileName).ToUpper
            Case ".BMP"
                bmp.Save(.FileName, ImageFormat.Bmp)
            Case ".GIF"
                bmp.Save(.FileName, ImageFormat.Gif)
            Case ".TIF"
                bmp.Save(.FileName, ImageFormat.Tiff)
            Case Else
                MessageBox.Show("サポートされていない形式です。", "Error", _
                    MessageBoxButtons.OK, MessageBoxIcon.Error)
        EndSelect
    End If
End With

どの形式のイメージでも保存することができますが、場合によっては圧縮率などの追加情報を指定する必要があるかもしれません(追加情報については.NET Framework SDKドキュメントを参照してください)。

18.2.2| イメージの表示

前節で見てきたとおり、DrawImageメソッドを使用してGraphicsオブジェクトにイメージを表示します。最も簡単な構文では、このメソッドの引数は表示されるイメージと、宛先領域の左上隅の座標をとります。

                  
' 座標(100,200)にイメージを表示します。
gr.DrawImage(bmp, 100, 200)

DrawImageメソッドは30種類のオーバーロードをサポートします。これらのオーバーロードの中には、イメージの描画される部分領域を指定するものもあります。

                  
' イメージの左半分のみを座標(20,20)に描画します。
gr.DrawImage(bmp, 20, 20, _
    New RectangleF(0, 0, bmp.Width / 2, bmp.Height), GraphicsUnit.Pixel)

別のオーバーロードでは、イメージ全体を指定された宛先の四角形にコピーします。ターゲットの四角形のサイズを変更することによって、コピー操作の間にイメージを拡大したり縮小したりできます。

                  
' 3倍の幅と2倍の高さを持つ宛先の四角形を作成します。
Dim rect As New RectangleF(20, 120, bmp.Width * 3, bmp.Height * 2)
' 拡大されたビットマップを描画します。
gr.DrawImage(bmp, rect)

宛先の四角形と元の四角形の両方を指定して、2つの効果を組み合わせることもできます。

                  
Dim destRect As New RectangleF(20, 320, bmp.Width * 1.5, bmp.Height)
' イメージの左上4分の1に四角形を作成します。
Dim sourceRect As New RectangleF(0, 0, bmp.Width / 2, bmp.Height / 2)
' 宛先の四角形にビットマップの一部を描画します。
gr.DrawImage(bmp, destRect, sourceRect, GraphicsUnit.Pixel)

図18-15は、これまでの3つのプログラムの効果を示しています。前節の座標変換で説明したとおり、DrawImageメソッドはページ変換ではなくワールド変換に影響されます。

図18-15 イメージ全体またはその一部のコピーと拡大

▲図18-15 イメージ全体またはその一部のコピーと拡大

次のプログラムのように、Graphics.FromImageメソッドでGraphicsオブジェクトを取得して、イメージの上に描画することもできます。ただし、この方法は、GIFやTIFFなど、一部のファイル形式をサポートしていません。

                  
' このプログラムは変数bmpが
' ビットマップの参照を保持していると想定しています。
Dim gr As Graphics = Graphics.FromImage(bmp)
' Graphicsオブジェクトに直接描画してから解放します。
gr.FillRectangle(Brushes.Red, 20, 20, 100, 100)
gr.Dispose()

BitmapDataオブジェクトを取得するBitmapクラスのLockBitsメソッドを使用すれば、より高度な処理を行うことができます。BitmapDataオブジェクトには、実際のピクセルのメモリアドレスがあります。残念なことに、Visual Basicはポインタをサポートしていないので、この言語でBitmapオブジェクトのメモリにアクセスすることはできません。したがって、C#を使用するかまたはアンマネージコードを呼び出す必要があります。このメソッドでロックされたBitmapオブジェクトは、UnlockBitsメソッドでロックを解除する必要があります。

18.2.3| イメージの反転、回転、傾斜

DrawImageメソッドは引数として3つのPoint構造体の配列をとることもできます。これらのPoint配列は、描画されるイメージの宛先領域として使用されるターゲットのGraphicsオブジェクトに、平行四辺形を定義します。3つのPointは、元のイメージの左上、右上、左下を宛先のどの場所にコピーするかを定義します。次の図のように、3つのPointを調整して、イメージの反転、回転、傾斜を行うことができます。

図18-16 イメージの反転、回転、傾斜

▲図18-16 イメージの反転、回転、傾斜

必要とする効果を得るために、これら3つのPointをどのように指定するべきかを理解するのは、簡単ではありません。たとえば、回転の効果は三角関数の計算を必要とします。そのため、イメージを1つまたは両方の軸に沿って反転させたり、度数で指定した角度で回転させたり、1つまたは両方の軸に沿って指定した分だけ傾斜させたりする、再利用可能な3つのルーチンを用意しました。

                  
Sub DrawFlipImage(ByVal gr As Graphics, ByVal bmp As Bitmap, _
    ByVal x As Single, ByVal y As Single, _
    ByVal flipX As Boolean, ByVal flipY As Boolean)
    ' 平行四辺形の頂点を定義します。
    Dim x0 As Single = x
    Dim y0 As Single = y
    Dim x1 As Single = x + bmp.Width
    Dim y1 As Single = y
    Dim x2 As Single = x
    Dim y2 As Single = y + bmp.Height
    ' 水平方向の反転を処理します。
    If flipX Then
        x0 = x + bmp.Width
        x1 = x
        x2 = x0
    End If
    ' 垂直方向の反転を処理します。
    If flipY Then
        y0 = y + bmp.Height
        y1 = y0
        y2 = y
    End If
    ' Point配列を作成します。
    Dim points() As Point = _
        {New Point(x0, y0), New Point(x1, y1), New Point(x2, y2)}
    ' 反転したイメージを描画します。
    gr.DrawImage(bmp, points)
End Sub

Sub DrawRotateImage(ByVal gr As Graphics, ByVal bmp As Bitmap, _
    ByVal x As Single, ByVal y As Single, ByVal angle As Single)
    ' 度数で示された角度を変換します。
    angle = angle / (180 / Math.PI)
    ' (x1,y1)と(x2,y2)の位置を探します。
    Dim x1 As Single = x + bmp.Width * Math.Cos(angle)
    Dim y1 As Single = y + bmp.Width * Math.Sin(angle)
    Dim x2 As Single = x - bmp.Height * Math.Sin(angle)
    Dim y2 As Single = y + bmp.Height * Math.Cos(angle)

    ' Point配列を作成します。
    Dim points() As Point = _
        {New Point(x, y), New Point(x1, y1), New Point(x2, y2)}
    ' 回転したイメージを描画します。
    gr.DrawImage(bmp, points)
End Sub

Sub DrawSkewImage(ByVal gr As Graphics, ByVal bmp As Bitmap, _
    ByVal x As Single, ByVal y As Single, _
    ByVal dx As Single, ByVal dy As Single)
    ' (x1,y1)と(x2,y2)の位置を探します。
    Dim x1 As Single = x + bmp.Width
    Dim y1 As Single = y + dy
    Dim x2 As Single = x + dx
    Dim y2 As Single = y + bmp.Height
    ' Point配列を作成します。
    Dim points() As Point = _
        {New Point(x, y), New Point(x1, y1), New Point(x2, y2)}
    ' 傾斜したイメージを描画します。
    gr.DrawImage(bmp, points)
End Sub

これら3つのルーチンを使用すれば、反転、回転、傾斜したイメージを描画するのは簡単なことです。たとえば、次のプログラムは、図18-17のような結果になります。

                  
DrawFlipImage(gr, bmp, 100, 20, False, False)
DrawFlipImage(gr, bmp, 300, 20, True, False)
DrawFlipImage(gr, bmp, 100, 120, False, True)
DrawFlipImage(gr, bmp, 300, 120, True, True)

DrawRotateImage(gr, bmp, 100, 220, 45)
DrawRotateImage(gr, bmp, 300, 220, 90)
DrawRotateImage(gr, bmp, 500, 220, 135)

DrawSkewImage(gr, bmp, 100, 400, -50, 0)
DrawSkewImage(gr, bmp, 300, 400, 0, 50)
DrawSkewImage(gr, bmp, 500, 400, -50, 50)

図18-17 反転、回転、傾斜したイメージ

▲図18-17 反転、回転、傾斜したイメージ

イメージを拡大すると、元のイメージのPointが、拡大された宛先イメージの複数のPointに割り当てられます。また、イメージを縮小すると、その反対になります。そのような場合、GDI+は、元のピクセルを補間(微調整)して、宛先ピクセルに割り当てます。Graphics.InterpolationModeプロパティで補間モードを指定すると、補間の方法を制御することができます。使用可能な補間モードは、Low、High、Bilinear、Bicubic、NearestNeighbor、HighQualityBilinear、HighQualityBicubicです(これらのモードについて詳しくは.NET Framework SDKドキュメントを参照してください)。

拡大・縮小をともなう変換がない必要がない場合には、宛先領域のサイズが元のイメージのサイズと同じであることを明示的に指定し、宛先となるGraphicsオブジェクトの1インチあたりのドット数が元のイメージと異なる場合に変換を行わないようにすることで、DrawImageメソッドのパフォーマンスを向上させることができます。次は、できるだけ速くイメージがコピーされるようにする簡単な例です。

                  gr.DrawImage(bmp, 20, 50, bmp.Width, bmp.Height)

または、DrawImageUnscaledメソッドはイメージを元のサイズのまま、ターゲットのPointやX-Y座標で指定された場所に描画します。

                  
gr.DrawImageUnscaled(bmp, 20, 50)

Rectangle構造体をイメージの部分領域として指定することもできます。

                  
gr.DrawImageUnscaled(bmp, New Rectangle(20, 50, 200, 100))

18.2.4| 透明と半透明のビットマップ

GDI+には、透明または半透明のビットマップを作成する方法が3つあります。第1の方法は、ビットマップ内の色から透明にする色を1つ選択します。第2の方法は、ビットマップ全体に対して透過度を選択します(Opacityプロパティでフォームの透過度を選択する方法とほとんど同じです)。第3の方法は、ビットマップ内の個々のピクセルに対してそれぞれ異なる透過度を設定します。

第1の方法でビットマップを作成するのが3つの中で最も簡単な方法です。必要なのは、MakeTransparentメソッドで透過色を指定するだけです。

                  
'ビットマップを作成して、透明にします。
Dim bmp As New Bitmap("logo.bmp")
bmp.MakeTransparent(Color.FromArgb(140, 195, 247))
gr.DrawImage(bmp, 20, 20)

図18-18の左のイメージは、この操作の効果を示しています。

図18-18 透明と半透明のビットマップ

▲図18-18 透明と半透明のビットマップ

すべてのピクセルが一定の透過度を持つビットマップを作成するには、適切なImageAttributesオブジェクトをDrawImageメソッドに渡します。ただしこれを行う前に、SetColorMatrixメソッドを使用して、5×5ピクセルの色の行列をImageAttributesオブジェクトに割り当てる必要があります。この操作を実行するために必要なコードは、簡単ではありません。色の行列はSingle配列になります。次は、透過度が0.8のイメージを描画するプログラムです(このプログラムは図18-18の中央のイメージを生成します)。

                  
Dim bmp As New Bitmap("logo.bmp")
' ビットマップ内のすべてのピクセルに共通する透過度を定義します。
Dim transparency As Single = 0.8
' (4,4)の位置に透過値がある5x5の行列を作成します。
Dim values()() As Single = {New Single() {1, 0, 0, 0, 0}, _
    New Single() {0, 1, 0, 0, 0}, _
    New Single() {0, 0, 1, 0, 0}, _
    New Single() {0, 0, 0, transparency, 0}, _
    New Single() {0, 0, 0, 0, 1}}
' 行列を使用して、新しいColorMatrixオブジェクトを初期化します。
Dim colMatrix As New ColorMatrix(values)
' ImageAttributesオブジェクトを作成して、その色の行列を割り当てます。
Dim imageAttr As New ImageAttributes()
imageAttr.SetColorMatrix(colMatrix, ColorMatrixFlag.Default, _
    ColorAdjustType.Bitmap)
' 指定したImageAttributesオブジェクトを使用して、ビットマップを描画します。
gr.DrawImage(bmp, New Rectangle(200, 20, bmp.Width, bmp.Height), _
    0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel, imageAttr)

半透明のビットマップを作成する最後の方法は、3つの中で最も柔軟な方法です。この方法は、イメージ内の個々のピクセルの透明度を表すアルファコンポーネントを制御します。GetPixelメソッドでイメージ内の個々のピクセルの色にアクセスして、SetPixelメソッドでその色(とアルファブレンドコンポーネント)を変更します。この方法は3つの方法の中で最も遅いです。しかし、この方法を使用すれば、次のプログラムで作成される図18-18の一番右のイメージのようなすばらしい結果を得ることができます。

                  
' 変数bmp3はビットマップのコピーです。
Dim bmp3 As Bitmap = bmp.Clone
Dim x, y As Single
' このForループはビットマップ内のすべてのピクセルを対象にします。
For x = 0 To bmp3.Width - 1
    For y = 0 To bmp3.Height - 1
        ' 現在の色を取得します。
        Dim oldColor = bmp3.GetPixel(x, y)
        ' 0(左端)から1(右端)までの透過値を設定します。
        Dim newColor As Color = Color.FromArgb(x / bmp3.Width * 256, oldColor)
        bmp3.SetPixel(x, y, newColor)
    Next
Next
gr.DrawImage(bmp3, 400, 20)

18.2.5| アイコン

アイコンは、透過色を含み、サイズがシステムによって決定される小さなビットマップのようなものです。System.Drawing名前空間にはIconクラスが含まれており、アイコンの読み込みや描画に使用します。このクラスはImageクラスから継承するものではないので、今までに見てきたメソッドを使用することはできません。Iconオブジェクトで実行できる操作はかなり限定されています。ファイル名またはStreamオブジェクトをアイコンのコンストラクタに渡してアイコンを読み込みます。

                  
Dim icon As New Icon("W95MBX01.ICO")

アイコンを取得するもう1つの方法は、SystemIconsクラスを使用することです。このクラスには、メッセージボックス用のアイコンや、その他いくつかのアイコンを取得するプロパティがあります。

                  
Dim icon2 As Icon = SystemIcons.Exclamation()
Dim icon3 As Icon = SystemIcons.WinLogo()

Iconオブジェクトを取得したら、DrawIconメソッドを使用してGraphicsオブジェクトに表示できます。このメソッドの引数はターゲット座標か宛先となる四角形をとります。

                  
' アイコンを元のサイズで描画します。
gr.DrawIcon(icon, 20, 20)
' アイコンを400%拡大して描画します。
gr.DrawIcon(icon, New Rectangle(100, 20, icon.Width * 4, icon.Height * 4))
' すべてのアイコンを破棄します。
icon.Dispose()

ほとんどの場合、実行が必要なのはアイコンの読み込みとその表示だけです。アイコンの回転や別の透過色の定義など、さらに高度なグラフィックス効果を得たい場合には、アイコンをビットマップに変換する必要があります。

                  
Dim icon As New Icon("W95MBX01.ICO")
' アイコンをビットマップに変換します。
Dim bmp As Bitmap = icon.ToBitmap
' 2番目の透過色を赤にして、イメージを描画します。
bmp.MakeTransparent(Color.Red)
gr.DrawImage(bmp, 20, 200)
' ビットマップとアイコンの両方を破棄します。
icon.Dispose()
bmp.Dispose()

GraphicsオブジェクトにはDrawIconUnstretchedメソッドもあり、このメソッドの引数は表示されるアイコンとRectangle構造体をとります。アイコンがターゲットの四角形に合うように拡大されることはありませんが、四角形よりも大きい場合にはクリッピングされます。

18.2.6| メタファイル

メタファイルは、Graphicsオブジェクトの一連の描画機能を持っているという点で、パスに似ており、イメージングと文字列操作も可能です。MetafileオブジェクトとGraphicsPathオブジェクトとの主な違いは、Metafileオブジェクトではハードディスクに一連の操作の保存と再読み込みができるということです。Metafileクラスのコンストラクタは39種類のオーバーロードを提供していますが、一般的な使用法では、作成するファイルの名前と、メタファイルが描画されるデバイスコンテキストのハンドルを指定します。

                  
' 現在のフォームでGraphicsオブジェクトを作成します。
Dim gr As Graphics = Me.CreateGraphics
' フォームのGraphicsオブジェクトと同じ機能を持つ、
' Metafileオブジェクトを作成します。
Dim mf As New Metafile("sample.emf", gr.GetHdc)

次の手順として、メタファイルと関連付けられる内部Graphicsオブジェクトを取得します。Graphicsオブジェクトでは、メタファイルの非表示のグラフィックス描画面にどんな形状や文字列でも描画することができます。

                  
' メタファイル内のGraphicsオブジェクトを取得します。
Dim mfgr As Graphics = Graphics.FromImage(mf)
' メタファイルのGraphicsオブジェクトにいくつかのグラフィックスを描画します。
mfgr.DrawLine(Pens.Blue, 20, 20, 200, 200)
mfgr.DrawRectangle(Pens.Red, 50, 50, 200, 120)
mfgr.FillEllipse(Brushes.Green, 70, 20, 200, 120)
' 文字列をArialフォントの20ピクセルで描画します。
Dim fnt As New Font("Arial", 20, FontStyle.Bold Or FontStyle.Italic)
mfgr.DrawString("Test String", fnt, Brushes.Yellow, 120, 100)

Metafileオブジェクトが破棄されるとき、関連付けられているファイルが更新されます。メタファイル内の描画命令をもう一度実行するのは、非常に簡単なことです。

                  
' フォームのGraphicsオブジェクトを作成します。
Dim gr As Graphics = Me.CreateGraphics
' メタファイルを読み取ります。
Dim mf As New Metafile("ample.emf")
' メタファイルを2つの異なる場所に描画します。
gr.DrawImage(mf, 0, 0)
gr.DrawImage(mf, 300, 100)
' すべてのオブジェクトを破棄します。
mf.Dispose()
gr.Dispose()

これらの操作の結果を図18-19で確認することができます。

図18-19 メタファイルの表示

▲図18-19 メタファイルの表示

メタファイルについて最後に注意すべき点として、メタファイルをもう一度実行するとき、ワールド変換やクリッピングなどに使用されるGraphicsオブジェクトのプロパティは、現在アクティブなプロパティではなく、メタファイルが記録されたときにアクティブだったプロパティになります。

18.3 | タイポグラフィ

タイポグラフィは、さまざまなフォントやスタイルでの文字列印刷に関連する.NET Frameworkの機能です。既にこの章の前の方にあるいくつかのプログラムで、テクスチャ出力の作成方法を見てきましたが、まだ操作の詳細については説明していませんでした。

GDI+は、TrueTypeフォントとOpenTypeフォントしかサポートしません。これらのフォントは拡大、縮小、回転、共有が可能であり、何の制限もなくフォーム(画面)とプリンタの両方で使用することができます。

18.3.1| フォントファミリ

GDI+の用語では、「フォントファミリ」は、基本デザインが同じで異なるスタイルを持つフォントのグループです。たとえばTahomaフォントファミリには、Tahoma標準、Tahoma太字、Tahoma斜体、Tahoma太字斜体が含まれます。InstalledFontCollectionオブジェクトのFamiliesプロパティを使用して、インストールされているすべてのフォントファミリを列挙することができます。FamiliesプロパティはFontFamilyオブジェクトの配列を返すので、このオブジェクトを列挙すれば、フォントファミリを調べることができます。

                  
' インストールされているフォントのコレクションを取得します。
Dim fonts As New System.Drawing.Text.InstalledFontCollection()
' インストールされているすべてのフォントファミリの配列を取得します。
Dim fontFamilies() As FontFamily = fonts.Families
' フォントファミリ名をコンマで区切った一覧を作成します。
Dim list As String
Dim fontFam As FontFamily
For Each fontFam In fontFamilies
    If list <> "" Then list &= ", "
    list &= fontFam.Name
Next
' [デバッグ]ウィンドウにフォントの一覧を表示します。
Debug.WriteLine(list)

インストールされているフォントファミリの配列を取得するもう1つの方法は、FontFamily.GetFamiliesメソッドを使用することです。このメソッドではGraphicsオブジェクトを指定し、そのデバイスにインストールされているフォントのみを取得します。したがって、フォームまたはプリンタ用のフォントのみを列挙することができます。

                  
' フォーム用にインストールされているフォントを列挙します。
Dim gr As Graphics = Me.CreateGraphics
Dim fontFamilies() As FontFamily = FontFamily.GetFamilies(gr)
gr.Dispose

FontFamilyクラスの3つの静的プロパティを使用して、標準のフォントファミリの参照を取得することができます。GenericMonospaceは等幅のFontFamilyオブジェクトを取得し、GenericSansSerifはサンセリフのFontFamilyオブジェクトを取得し、GenericSerifはセリフのFontFamilyオブジェクトを取得します。

FontFamilyオブジェクトを取得したら、IsStyleAvailableメソッドを使用して、指定されたスタイルが使用可能かどうかを判断することができます。

                  
If fontFam.IsStyleAvailable(FontStyle.Bold) Then
    ' このフォントファミリは太字をサポートしています。
    ・
    ・
    ・
End If

いくつかのフォントでは標準スタイルが利用できないことに注意してください。他にも、GetEmHeightGetCellAscentGetCellDescentGetLineSpacingなど、FontFamilyクラスのメソッドを使用して、フォントメトリックの情報を特定することができます(これらのメソッドについて詳しくは、.NET Framework SDKドキュメントを参照してください)。

18.3.2| 文字列の描画

印刷処理ではFontFamilyオブジェクトを使用することはできません。FontFamilyオブジェクトには、特定のサイズや、下線や取り消し線などの属性がありません。次のプログラムで示されているように、多数の方法でFontオブジェクトを作成することができます。

                  
' ファミリとポイントで示したサイズからフォントを作成します。
Dim font1 As New Font("Arial", 12)
' ファミリ、サイズ、スタイルからフォントを作成します。
Dim font2 As New Font("Arial", 14, FontStyle.Bold)
' ファミリ、サイズ、2つを組み合わせたスタイルからフォントを作成します。
Dim font3 As New Font("Arial", 16, FontStyle.Italic Or FontStyle.Underline)
' ファミリと別の単位のサイズからフォントを作成します。
Dim font4 As New Font("Arial", 10, FontStyle.Regular, GraphicsUnit.Millimeter)
' フォントファミリを作成してから、そのファミリのフォントを作成します。
Dim fontFam As New FontFamily("Courier New")
Dim font5 As New Font(fontFam, 18, FontStyle.Italic)

Fontオブジェクトのコンストラクタに渡すサイズ指定の引数は、ピクセルサイズではなくメトリックサイズです。指定されたサイズの結果は現在のページ変換には左右されません(ただし、文字列のサイズはワールド変換に左右されます)。次のように、GraphicsUnit列挙体の引数を指定して、単位を特定することができます。

                  
Dim font1 As New Font("Arial", 12, GraphicsUnit.Pixel)
Dim font2 As New Font("Arial", 14, FontStyle.Bold, GraphicsUnit.Pixel)
・
・
・
・

フォントサイズにGraphicsUnit.Displayを使用することはできません。

GraphicsオブジェクトのDrawStringメソッドを使用して文字列を描画します。最も簡単な形式では、このメソッドの引数は表示される文字列、Fontオブジェクト、Brushオブジェクト、文字列が描画されるPointの座標をとります。

                  
' ...(前述のコードの続き)...
Dim gr As Graphics = Me.CreateGraphics
gr.DrawString("Arial 12 Regular", font1, Brushes.Black, 20, 20)
gr.DrawString("Arial 14 Bold", font2, Brushes.Black, 20, 60)
gr.DrawString("Arial 16 Italic & Underline", font3, Brushes.Black, 20, 100)
gr.DrawString("Arial 10 millimeters", font4, Brushes.Black, 20, 140)
' 宛先ポイントにはPointF構造体も指定できます。
gr.DrawString("Courier 18 Italic", font5, Brushes.Black, New PointF(20, 200))
' Fontオブジェクトが必要なくなれば、破棄します。
font1.Dispose()
font2.Dispose()
font3.Dispose()
font4.Dispose()
font5.Dispose()

このプログラムの結果は、図18-20で確認することができます。

図18-20 異なるサイズと属性でフォントを表示

▲図18-20 異なるサイズと属性でフォントを表示

連続行に文字列を表示する場合には、FontGetHeightメソッドを利用することができます。このメソッドは指定されたGraphicsオブジェクトでのフォントの高さを返します。たとえば、次のプログラムは前述のプログラムと似ていますが、行間隔を近づけて表示します。

                  
' 同じフォント出力を、より狭い行間隔で再描画します。
Dim y As Integer = 20
gr.DrawString("Arial 12 Regular", font1, Brushes.Black, 20, y)
y += font1.GetHeight(gr)
gr.DrawString("Arial 14 Bold", font2, Brushes.Black, 20, y)
y += font2.GetHeight(gr)
gr.DrawString("Arial 16 Italic & Underline", font3, Brushes.Black, 20, y)
y += font3.GetHeight(gr)
gr.DrawString("Arial 10 millimeters", font4, Brushes.Black, 20, y)
y += font4.GetHeight(gr)
gr.DrawString("Courier 18 Italic", font5, Brushes.Black, New PointF(20, y))

Penオブジェクトではなく、Brushオブジェクトを使用して文字列を描画するので、次のプログラムのようにテクスチャブラシ、ハッチブラシ、またはグラデーションブラシを使用して、すばらしい効果を簡単に作成することができます(図18-21は、このプログラムの結果です)。

                  
' テクスチャブラシを作成します。
Dim br As New TextureBrush(Image.FromFile("greenstone.bmp"))
' 大きいフォントを作成します。
Dim fnt As New Font("Arial", 50, FontStyle.Bold, GraphicsUnit.Millimeter)
' テクスチャで文字列を塗りつぶします。
gr.DrawString("Textured", fnt, br, 20, 20)
gr.DrawString("Text", fnt, br, 20, 200)

図18-21 テクスチャブラシで文字列を描画

▲図18-21 テクスチャブラシで文字列を描画

18.3.3| 文字列の配置

DrawStringメソッドは、引数としてRectangleF構造体をとることもできます。次のプログラムのように、このメソッドは引数で指定された四角形の中に文字列を描画します。

                  
Dim msg As String = "RectangleF構造体の中に、どのように長い文字列が" _
    & "格納されるかという、サンプルです。"
Dim fnt As New Font("MS UI Gothic", 12)
Dim rectF As RectangleF

' 四角形の中に文字列を描画します。
rectF = New RectangleF(20, 20, 140, 160)
gr.DrawString(msg, fnt, Brushes.Black, rectF)
' 境界となる四角形も表示します。
gr.DrawRectangle(Pens.Red, 20, 20, 140, 160)

StringFormatオブジェクトを初期化してDrawStringメソッドに指定すれば、RectangleF構造体の中にある文字列の水平方向と垂直方向の配置を制御できます。StringFormatオブジェクトのAlignmentプロパティは水平方向の配置に影響を与え、Near(既定、左揃え)、Center(中央揃え)、Far(右揃え)が可能です。LineAlignmentプロパティは垂直方向の配置に影響を与え、同じようにNear(既定、上揃え)、Center(中央揃え)、Far(下揃え)が可能です。

                  
' ...(前述のコードの続き)...

' 文字列を再び描画し、右揃えにします。
Dim strFormat As New StringFormat()
strFormat.Alignment = StringAlignment.Far
' 形式を指定して四角形の中に文字列を描画します。
rectF = New RectangleF(220, 20, 140, 160)
gr.DrawString(msg, fnt, Brushes.Black, rectF, strFormat)
' 境界となる四角形も表示します。
gr.DrawRectangle(Pens.Red, 220, 20, 140, 160)

' 文字列を再び表示し、水平方向、垂直方向ともに中央に揃えます。
strFormat.Alignment = StringAlignment.Center
strFormat.LineAlignment = StringAlignment.Center
' 形式を指定して四角形の中に文字列を描画します。
rectF = New RectangleF(420, 20, 140, 160)
gr.DrawString(msg, fnt, Brushes.Black, rectF, strFormat)
' 境界となる四角形も表示します。
gr.DrawRectangle(Pens.Red, 420, 20, 140, 160)

このコード例の結果は図18-22で確認することができます。

図18-22 境界となる四角形の中に文字列を配置

▲図18-22 境界となる四角形の中に文字列を配置

境界の四角形の大きさを決定するには、GraphicsオブジェクトのMeasureStringメソッドを使用します。このメソッドの最も簡単なオーバーロードは、文字列とFontオブジェクトをとります。

                  
' 文字列を1行で表示するのに必要な領域を決定します。
Dim size As SizeF = gr.MeasureString(msg, fnt)
Debug.WriteLine(size.ToString)

ほとんどの場合、文字列を折り返して指定された幅を越えないようにしたいので、幅を示す追加の引数を指定する必要があります。

                  
' 文字列幅が200ピクセルを越えられない場合、
' 境界の四角形にはどれだけの高さが必要かを決定します。
Dim size As SizeF = gr.MeasureString(msg, fnt, 200)
Debug.WriteLine(size.ToString)

18.3.4| テクスチャの種類

StringFormatオブジェクトのその他のプロパティは、文字列の表示方法に影響を与えます。たとえば、Trimmingプロパティは、全体を表示するには長すぎる文字列を文字と単語のどちらでトリムするか、それに省略記号(...)を文字列の表示可能領域の最後に挿入するかどうかを指定します(この機能は英単語を想定しています)。

                  
' 文字列は単語単位でトリムします。
strFormat.Trimming = StringTrimming.Word
' 文字列は文字単位でトリムされ、その後ろに省略記号が挿入されます。
strFormat.Trimming = StringTrimming.EllipsisCharacter
' 省略記号が文字列の中央で使用されます。
' (長いファイルのパスを表示するのに役立ちます)。
strFormat.Trimming = StringTrimming.EllipsisPath

FormatFlagsプロパティはビットコード値を使用でき、文字列の印刷方法をさらに制御します。

                  
' 完全な行のみが表示されるようにします。
' (最後の行が境界の四角形に完全には表示できない場合、その行は表示されません)。
strFormat.FormatFlags = StringFormatFlags.LineLimit

' 文字列を垂直(縦書き)に表示します。
strFormat.FormatFlags = StringFormatFlags.DirectionVertical
gr.DrawString(msg, fnt, Brushes.Black, 20, 20, strFormat)

また、GraphicsオブジェクトのRotateTransformメソッドを使用すれば、どんな角度にでも文字列を回転させることができます。

文字列を表示するときにもう1つ実行できることは、タブストップを使用して、図18-23のようにデータの列を揃えることです。タブストップを作成するには、タブ記号と改行記号を含む文字列を作成し、各タブストップの位置を格納するSingle配列を用意して、その配列をStringFormatオブジェクトのSetTabStopsメソッドに指定します。

                  
' タブ記号と改行記号のあるメッセージを作成します。
Dim msg As String = String.Format("{0}列 1{0}列 2{0}列 3{1}" _
    & "行 1{0}セル (1,1){0}セル (1,2){0}セル (1,3){1}" _
    & "行 2{0}セル (2,1){0}セル (2,2){0}セル (3,3){1}", _
    ControlChars.Tab, ControlChars.CrLf)

Dim fnt As New Font("MS UI Gothic", 12)
Dim strFormat As New StringFormat()
' タブストップを設定します。
Dim tabStops() As Single = {80, 140, 200}
strFormat.SetTabStops(0, tabStops)
' タブストップを使用して文字列を描画します。
gr.DrawString(msg, fnt, Brushes.Black, 20, 20, strFormat)
' Fontオブジェクトを破棄します。
fnt.Dispose()

図18-23 タブストップの使用

▲図18-23 タブストップの使用

18.3.5| アンチエイリアシング

アンチエイリアシングは、グラフィックス出力を処理して線がぎざぎざに表示されないようにする方法です。線の色と背景の色の中間色を使用することで、滑らかな線を表現します。たとえば、白の背景に黒のぎざぎざの線を描画する場合、その境界付近に灰色のピクセルを使用して、滑らかに見せます。

図18-24の右にある小さな楕円は、左にある楕円をアンチエイリアシングしたものです。おそらく、右の楕円の方が上手に定義されているように見えると思います。2つの楕円を拡大すれば(図の下の部分を参照)、アンチエイリアシングの方では灰色のピクセルを使用して境界を滑らかにしていることがわかります。

図18-24 アンチエイリアシングを使用した形状

▲図18-24 アンチエイリアシングを使用した形状

GDI+は、文字列と形状の両方のアンチエイリアシングをサポートしています。形状の場合は、AntiAliasモードやHighQualityモードなどを選択できます。AntiAliasモードはどの形式のディスプレイでもうまく機能しますが、HighQualityモードはLCD画面のサブピクセルの解像度を利用します。LCD画面上のピクセルは、それぞれにオンまたはオフに切り替えられる3つのストライプに分けられるので、より精巧な効果を得ることができます(これは、Microsoft ClearType技術で使用されています)。HighQualityモードは通常のCRTディスプレイには何の効果もありません。余分な処理時間が必要とされるために、AntiAliasモードは通常の表示より速度が遅く、HighQualityモードはさらに遅いです。

線と文字列のどちらを処理するかに応じて、Graphicsオブジェクトの2つのプロパティを使い分けます。線や曲線を処理する場合は、SmoothingModeプロパティを使用します。たとえば、次は図18-24にある2つの楕円を生成するプログラムです。

                  
' 最初の楕円を通常のモードで描画します。
gr.DrawEllipse(Pens.Black, 20, 20, 100, 60)
' 2つ目の楕円をAntiAliasモードで描画します。
gr.SmoothingMode = Drawing.Drawing2D.SmoothingMode.AntiAlias
gr.DrawEllipse(Pens.Black, 140, 20, 100, 60)

PixelOffsetModeプロパティを使用すれば、グラフィックスのアンチエイリアシングをさらに制御できます(詳しくは.NET Framework SDKドキュメントを参照してください)。

文字列のアンチエイリアシング(レンダリング)は、TextRenderingHintプロパティによって制御されます。次は、図18-25の結果を生成するプログラムです。

                  
Dim fnt As New Font("Arial", 14)
gr.DrawString("Regular Text", fnt, Brushes.Black, 20, 200)
' 標準のアンチエイリアシング
gr.TextRenderingHint = Drawing.Text.TextRenderingHint.AntiAlias
gr.DrawString("Standard Anti-aliasing", fnt, Brushes.Black, 20, 260)
' ClearTypeアンチエイリアシング
gr.TextRenderingHint = Drawing.Text.TextRenderingHint.ClearTypeGridFit
gr.DrawString("ClearType Anti-aliasing", fnt, Brushes.Black, 20, 320)
' Fontオブジェクトを破棄します。
fnt.Dispose()

図18-25 通常の文字列出力と比較した2種類のアンチエイリアシング

▲図18-25 通常の文字列出力と比較した2種類のアンチエイリアシング

残念ですが、GDI+の優れた機能をすべて説明するだけの十分な紙数がないので、これで説明を終わらなければなりません。たとえば、Matrixオブジェクトの変換、グラフィックスコンテナ、高度な色の操作については説明していません。しかし、.NET Frameworkのこの領域に潜むすばらしい可能性を利用できるだけの十分な知識は得られたと思います。次の章では、Win32アプリケーションで実行できることについてさらに探求していきます。