4.9 曲線
4.9.1 線分で図形を記述する
Path を利用することで、ほとんどの図形を描画することができるようになりますが、これまでの内容では曲線を描画することはできませんでした。曲線のようなより複雑な図形を表現するには、最も柔軟な図形表現が可能な System.Windows.Media.PathGeometry クラスを使います。このクラスもまた Geometry クラスから派生しているためパスに設定して利用することができます。
System.Object System.Windows.Threading.DispatcherObject System.Windows.DependencyObject System.Windows.Freezable System.Windows.Media.Animation.Animatable System.Windows.Media.Geometry System.Windows.Media.PathGeometry
[ContentPropertyAttribute("Figures")] public sealed class PathGeometry : Geometry
PathGeometry は、少々難解なクラスです。このクラスには、語りを現すオブジェクトを内部で保有する機能があり、PathGeometry は図形を記述することができる図形という意味があります。これは、他の Geometry のサブクラスのように、Geometry を組み合わせて作成するものではなく、PathGeometry 単体で、まったく新しい図形を作成します。このクラスを利用すれば、線、多角形、曲線、長方形、楕円など、あらゆる図形を表現することができます。
PathGeometry のコンストラクタはオーバーロードされていますが、インスタンス生成後にプロパティから情報を設定することができるので、この場ではパラメータを渡さないコンストラクタのみを使用します。
public PathGeometry ()
PathGeometry は、他の Geometry とは異なり、特定の形を既定で提供するものではありません。Path クラスのように、PathGeometry にも論理的に表現された形を表すオブジェクトを設定して、開発者が自由に新しい模様を記述することができます。このとき、Path と PathGeometry の役割が重複していることに混乱するかもしれません。Path は模様を表す抽象的な Geometry によって形が決定されるのに対し、PathGeometry は、さらに抽象化された線を組み合わせることによって Geometry を決定します。
究極的には PathGeometry を用いることで、これまで作成してきたあらゆる Geometry を表現することができ、PathGeometry を用いた Path を作成することで、どのような図形も描画することができるようになります。
PathGeometry は、線分によって表現された形を表すオブジェクトを追加することで、独自の Geometry を表現することができます。このときの線分をセグメントとも呼びます。複数のセグメントを組み合わせて 1 つの形を表現し、その形を組み合わせて 1 つの PathGeometry が構築されます。
セグメントを組み合わせた 1 つの形は Figures プロパティが返すコレクションに対して追加することができます。
public PathFigureCollection Figures { get; set; }
Figures プロパティが返す PathFigureCollection 型のオブジェクトは System.Windows.Media.PathFigure クラスのオブジェクトの配列を管理するコレクションです。PathFigure オブジェクトを作成し、Figures プロパティが返すコレクションに Add() メソッドから追加することで、PathGeometry に形を追加することができます。
System.Object System.Windows.Threading.DispatcherObject System.Windows.DependencyObject System.Windows.Freezable System.Windows.Media.Animation.Animatable System.Windows.Media.PathFigure
[LocalizabilityAttribute(LocalizationCategory.None, Readability=Readability.Unreadable)] [ContentPropertyAttribute("Segments")] public sealed class PathFigure : Animatable, IFormattable
PathFigure クラスの役割は、複数のセグメントを内部で保有して 1 つの形を表現することです。つまり、セグメントは単純な線分を表し、PathFigure が線分を組み合わせた形を表現し、そして PathGeometry が PathFigure を組み合わせた模様を表現するという階層的な関係を持ちます。
PathFigure クラスのコンストラクタはオーバーロードされていますが、この場では、パラメータを受け取らないコンストラクタを使用します。
public PathFigure ()
PathFigure クラスは、複数の線分を組み合わせて形を表現します。例えば、長方形を表す PathFigure オブジェクトは、4 本の線分によって構築されることになります。このとき、最初の線がどこから始まるのかという座標を設定しなければなりません。PathFigure の開始座標は StartPoint プロパティから設定します。
public Point StartPoint { get; set; }
StartPoint プロパティは、既定で (0, 0) 座標が設定されています。
次に、PathFigure に対して任意の数の線分を追加して、形を表現しなければなりません。線分を表すオブジェクトは Segments プロパティから追加することができます。
public PathSegmentCollection Segments { get; set; }
Segments プロパティが返す PathSegmentCollection オブジェクトは、線分を表す System.Windows.Media.PathSegment クラスのオブジェクトの配列を管理するコレクションです。このオブジェクトに、任意の数の PathSegment オブジェクトを Add() メソッドから追加できます。
System.Object System.Windows.Threading.DispatcherObject System.Windows.DependencyObject System.Windows.Freezable System.Windows.Media.Animation.Animatable System.Windows.Media.PathSegment
[LocalizabilityAttribute(LocalizationCategory.None, Readability=Readability.Unreadable)] public abstract class PathSegment : Animatable
PathSegment クラスは、PathFigure に追加する線分を表す抽象クラスです。線分が単純な直線なのか、多角形なのか、あるいは弧や曲線なのかは、このクラスのサブクラスによって実装されています。
この場では、最も単純な直線を表す System.Windows.Media.LineSegment クラスを用いて、PathGeometry クラスの機能を確認してみましょう。
System.Object System.Windows.Threading.DispatcherObject System.Windows.DependencyObject System.Windows.Freezable System.Windows.Media.Animation.Animatable System.Windows.Media.PathSegment System.Windows.Media.LineSegment
public sealed class LineSegment : PathSegment
LineSegment クラスのコンストラクタはオーバーロードされていますが、座標はプロパティからも設定できるので、この場ではパラメータを渡しません。
public LineSegment ()
LineSegment が表す線分の座標は Point プロパティから設定することができます。ただし、Line クラスのように始点と終点を表す 2 つの座標を設定するのではなく、終点のみを設定する点が LineSegment の大きな特徴です。
public Point Point { get; set; }
PathFigure オブジェクトは、StartPoint プロパティに設定した座標からはじまって、全ての線分が連続していなければなりません。PathFigure は、線分を用いて 1 つの形を表すためのオブジェクトなので、PathFigure が表す輪郭線に隙間を空けることはできないのです。
LineSegment も含め、全てのセグメントは直前に追加されたセグメントの終点から開始されます。最初に追加される LineSegment オブジェクトは、PathFigure の StartPoint プロパティの座標を始点に、LineSegment オブジェクト自身の Point プロパティの座標を終点に線を引きます。次の LineSegment オブジェクトは、直前の LineSegment の Point プロパティの座標を始点に線を引きます。
using System; using System.Windows; using System.Windows.Media; using System.Windows.Shapes; class Test { [STAThread] public static void Main() { LineSegment line1 = new LineSegment(); LineSegment line2 = new LineSegment(); line1.Point = new Point(410, 210); line2.Point = new Point(10, 210); PathFigure figure = new PathFigure(); figure.StartPoint = new Point(210, 10); figure.Segments.Add(line1); figure.Segments.Add(line2); PathGeometry geometry = new PathGeometry(); geometry.Figures.Add(figure); Path path = new Path(); path.Data = geometry; path.Stroke = Brushes.Black; Window wnd = new Window(); wnd.Content = path; Application app = new Application(); app.Run(wnd); } }
コード1は、2 本の線分からなる PathFigure を作成し、これを PathGeometry に追加し、さらに、この PathGeometry を Path オブジェクトに追加して図形を表示しています。もちろん、単純に 2 本の線を描画するだけが目的なら Line や Polyline クラスを用いるべきですが、この機能を用いることで、複雑な図形を再利用可能な形で抽象的に管理することができます。
コード1の場合、2 本の線を追加しているだけなので図形は開いています。確実に図形を閉じたい場合、PathFigure クラスの IsClosed プロパティを true に設定してください。
public bool IsClosed { get; set; }
このプロパティの既定の値は false に設定されています。このプロパティに true を設定している場合、最後の線分の終点から StartPoint の座標に向かって直線が引かれ図形は閉じられます。
4.9.2 ベジェ曲線
PathSegment から派生しているサブクラスは他にもいくつか存在しますが、この場では割愛させていただきます。基本的には LineSegment クラスと同じ容量で、連続した直線や円弧などを設定することができます。
PathGeometry を用いて表示する図形の中でも、特に柔軟性があり重要なのは曲線の表現です。曲線を表示するには、3 次ベジェ曲線を表す System.Windows.Media.BezierSegment クラスを用います。
System.Object System.Windows.Threading.DispatcherObject System.Windows.DependencyObject System.Windows.Freezable System.Windows.Media.Animation.Animatable System.Windows.Media.PathSegment System.Windows.Media.BezierSegment
public sealed class BezierSegment : PathSegment
ベジェ曲線とは、コンピュータ上の図形で曲線を表現するときに用いられる一般的な曲線の表現方法で、Windows 32 API の頃から利用されています。WPF 以外のプログラミングモデルでも多く採用されている手法なので、グラフィックス系の API を一度でも触れたことがあれば、経験したことがあるはずです。
このクラスのコンストラクタはオーバーロードされていますが、この場ではパラメータを渡さないコンストラクタを利用します。
public BezierSegment ()
3 次ベジェ曲線は、始点と終点の間に 2 つの制御点を持ちます。制御点は、曲線の方向と曲がり具合の強さを決定します。制御点が始点や終点から離れているほど、曲線は強く曲がります。
BezierSegment において、曲線の始点は直前のセグメントの終点です。なので、残りは 2 つの制御点と終点の座標を設定しなければなりません。最初の制御点と、2 つ目の制御点は Point1 プロパティと Point2 プロパティで設定し、終点の座標は Point3 プロパティで設定します。
public Point Point1 { get; set; }
public Point Point2 { get; set; }
public Point Point3 { get; set; }
BezierSegment オブジェクトの設定が終われば、後は BezierSegment オブジェクトを PathFigure に追加して表示するだけです。これで、曲線を表す Geometry を作成することができます。
using System; using System.Windows; using System.Windows.Media; using System.Windows.Shapes; class Test { [STAThread] public static void Main() { BezierSegment bezier = new BezierSegment(); bezier.Point1 = new Point(160, 10); bezier.Point2 = new Point(260, 210); bezier.Point3 = new Point(410, 110); PathFigure figure = new PathFigure(); figure.StartPoint = new Point(10, 110); figure.Segments.Add(bezier); PathGeometry geometry = new PathGeometry(); geometry.Figures.Add(figure); Path path = new Path(); path.Data = geometry; path.Stroke = Brushes.Black; Window wnd = new Window(); wnd.Content = path; Application app = new Application(); app.Run(wnd); } }
コード2は、上下に緩やかに曲がる曲線を描画しています。PathFigure の開始点である StartPoint プロパティには、座標 (10, 110) を設定し、曲線の終点である Point3 プロパティには (410, 110) を設定しているため、この 2 つの点は水平な直線となります。この線を上下に曲げるために、最初の制御点に (160, 10)、2 番目の制御点に (260, 210) を設定しています。座標 (160, 10) は、水平線よりも上に位置する座標であり、(260, 210) は水平線よりも下に位置しています。線は、これら制御店の方向に引っ張られるような形で曲げられ、曲線となっています。