WisdomSoft - for your serial experiences.

4.4 複数線と多角形

連続した線分と多角形の描画について、加えて複雑に交差する多角形の塗りつぶし規則について解説します。

4.4.1 連続する線を描画する

1 本の線を描画するだけならば Line クラスが適していますが、連続する複数の線を引く場合は Line の配列を用意するよりも、連続した線を表す System.Windows.Shapes.Polyline クラスを使ったほうが効率的です。

System.Windows.Shapes.Polyline クラス
System.Object 
   System.Windows.Threading.DispatcherObject 
     System.Windows.DependencyObject 
       System.Windows.Media.Visual 
         System.Windows.UIElement 
           System.Windows.FrameworkElement 
             System.Windows.Shapes.Shape 
              System.Windows.Shapes.Polyline
public sealed class Polyline : Shape

Polyline クラスのコンストラクタは、パラメータを受け取りません。

Polyline クラスのコンストラクタ
public Polyline ()

Polyline オブジェクトに複数の線を設定するには Points プロパティを使います。

Polyline クラス Points プロパティ
public PointCollection Points { get; set; }

Points プロパティから、繋がった線の座標を提供する System.Windows.Media.PointCollection クラスのオブジェクトを設定または取得できます。Polyline オブジェクトは、このプロパティに設定されている座標に従って線を描画します。

System.Windows.Media.PointCollection クラス
System.Object 
   System.Windows.Threading.DispatcherObject 
     System.Windows.DependencyObject 
       System.Windows.Freezable 
        System.Windows.Media.PointCollection
[TypeConverterAttribute(typeof(PointCollectionConverter))] 
public sealed class PointCollection : Freezable,
	IFormattable, IList, ICollection, 
	IList<Point>, ICollection<Point>, IEnumerable<Point>, IEnumerable

PointCollection クラスは、IList や ICollection インターフェイスを実装する座標のコレクションです。座標を表す Point 構造体の値を Add() メソッドから追加したり Remove() メソッドで削除することができます。PointCollection クラスのオブジェクトに追加した個々の Point が連続した線分の座標となります。Polyline は、PointCollection に追加された座標の順番に、繋がった線分を描画します。

コード1
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;

class Test {
	[STAThread]
	public static void Main() {
		Polyline lines = new Polyline();
		lines.Stroke = Brushes.Black;
		lines.Points.Add(new Point(10, 10));
		lines.Points.Add(new Point(100, 200));
		lines.Points.Add(new Point(200, 10));
		lines.Points.Add(new Point(300, 200));

		Window wnd = new Window();
		wnd.Content = lines;

		Application app = new Application();
		app.Run(wnd);
	}
}
実行結果
コード1 実行結果

コード1は、4 つの Point を保有する Polyline を描画しています。Line オブジェクトの配列を管理するよりも Polyline を使ったほうが複数の連続した線を効率的に管理することができます。

4.4.2 多角形

多角形を描画するには System.Windows.Shapes.Polygon クラスを使います。多角形は、Polyline のように複数の線を描画しますが、最後の座標から最初の座標に向かって線を描画して図形を閉じる点で Polyline とは異なります。Polygon は、閉じた図形なので内部を保有しているため、Fill プロパティの設定に基づいて多角形の内面を塗りつぶすことができます。

System.Windows.Shapes.Polygon クラス
System.Object 
   System.Windows.Threading.DispatcherObject 
     System.Windows.DependencyObject 
       System.Windows.Media.Visual 
         System.Windows.UIElement 
           System.Windows.FrameworkElement 
             System.Windows.Shapes.Shape 
              System.Windows.Shapes.Polygon
public sealed class Polygon : Shape

Polygon クラスのコンストラクタは、パラメータを受け取りません。 

Polygon クラスのコンストラクタ
public Polygon ()

Polygon クラスもまた、複数の線の座標を設定するときに Points プロパティを利用します。使い方は Polyline クラスの Points プロパティとまったく同じです。

Polygon クラス Points プロパティ
public PointCollection Points { get; set; }

Polygon クラスは、Polyline と同様に、Points プロパティに設定されている各座標に従って線を描画します。Polyline との違いは、PointCollection の最後の座標から、一番最初の座標に向かって線が描画され、自動的に図形が閉じられるという点です。

コード2
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;

class Test {
	[STAThread]
	public static void Main() {
		Polygon lines = new Polygon();
		lines.Stroke = Brushes.Black;
		lines.Fill = Brushes.Red;
		lines.Points.Add(new Point(10, 10));
		lines.Points.Add(new Point(100, 200));
		lines.Points.Add(new Point(200, 10));
		lines.Points.Add(new Point(300, 200));

		Window wnd = new Window();
		wnd.Content = lines;

		Application app = new Application();
		app.Run(wnd);
	}
}
実行結果
コード2 実行結果

コード2は、コード1を少しだけ改良したプログラムです。Polyline クラスの変わりに Polygon クラスを使っている点を除けば、Points プロパティに設定している値などは全てコード1と同じです。しかし、Polygon クラスの場合は、最後の座標 (300, 200) から、最初の座標 (10, 10) に向かって線が引かれ、図形が閉じられています。

また、このプログラムでは Fill プロパティに赤いブラシを設定しています。Polygon は閉じた図形なので Polyline とは異なり、閉じられた内部を指定したブラシで塗りつぶすことができます。

4.4.3 塗りつぶしルール

Polygon オブジェクトによる多角形の場合、線が重なりある一部の空間が内部と解釈するべきなのかどうか、曖昧になることがあります。例えば、次のような多角形を Polygon オブジェクトで定義したと考えてください。

図1 複雑な多角形
複雑な多角形

この多角形は、多角形の内部に交差する長方形を保有しています。この交差している長方形は図形の内部と考えるべきでしょうか。こうした、曖昧な内部を塗りつぶすかどうかは FillRule プロパティから設定することができます。

Polygon クラス FillRule プロパティ
public FillRule FillRule { get; set; }

FillRule は、多角形の内部の解釈方法を表すルールを System.Windows.Media.FillRule 列挙体のメンバから選択します。

System.Windows.Media.FillRule 列挙体
public enum FillRule

FillRule 列挙体には、交差している内部を塗りつぶさない EvenOdd メンバと、交差している内部も塗りつぶす Nonzero メンバの 2 つが用意されています。既定では FillRule プロパティに EvenOdd メンバが設定されているため、交差している内部を塗りつぶすことはありません。

通常 Nonzero メンバを設定している場合は、交差している内部も含めて、多角形の内側全体を塗りつぶすため、簡単に考えるならば上記の説明でも十分ですが、正しくは EvenOdd も Nonzero も定められた方法によって図形の内部かどうかを判定しています。Nonzero の場合も、一部の図形では内部が塗りつぶされないことがあります。

EvenOdd は、奇数の辺から偶数の辺の間にある閉じた空間を塗りつぶし、偶数の辺から奇数の辺にある空間は塗りつぶしません。この考え方は、どこからでも良いので多角形の上に線を引き、線と交わる辺の数を数えると分かりやすいでしょう。

図2 複雑な多角形
EvenOdd の判定

図2は、多角形の上に 1 本の線を引き、線と交わる多角形の辺を順に数えています。この辺の番号が奇数から偶数の間にある内側の空間は塗りつぶされますが、偶数から奇数の間にある内側は塗りつぶされません。ここから、1 と 2 の辺の間の多角形は塗りつぶされますが、2 と 3 の間の長方形は塗りつぶされない、ということが分かります。

Nonzero は、さらに複雑な性質を持ちます。Nonzero は、内部の空間から完全に多角形の外であると解釈できる空間まで線を引き、引いた線と交差する辺の方向を調べます。線と交差する辺の方向がそれぞれ異なっていれば内部が塗りつぶされますが、方向の数が一致する場合は塗りつぶされません。

辺の方向は、Polygon オブジェクトに追加された座標の順番で決定します。例えば、座標 (10, 10) から (400, 10) までの線は、座標 (10, 10) から (400, 10) までの方向に向かっていると考えます。

図3 複雑な多角形
Nonzero の判定

図3は、多角形の辺の方向を表しています。この図の、多角形の内部から外に向かって線を引いている面は塗りつぶされません。外に向かって引いている線に交差する辺の方向を見ると、水平方向に引いた線に交差する辺は、上に向かっている辺が 1 つ、下に向かっている辺の数が 1 つです。同様に、垂直に引いた線に交差する辺は、右に向かっている辺が 1 つ、左に向かっている辺が 1 つです。双方共に、それぞれの方向に向かっている辺の数が一致しているため、内部は塗りつぶされません。

コード3
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

class Test {
	[STAThread]
	public static void Main() {
		Polygon lines1 = new Polygon();
		lines1.Stroke = Brushes.Black;
		lines1.Fill = Brushes.Red;

		lines1.Points.Add(new Point(10, 10));
		lines1.Points.Add(new Point(110, 10));
		lines1.Points.Add(new Point(110, 210));
		lines1.Points.Add(new Point(60, 210));
		lines1.Points.Add(new Point(60, 60));
		lines1.Points.Add(new Point(210, 60));
		lines1.Points.Add(new Point(210, 160));
		lines1.Points.Add(new Point(10, 160));
		lines1.Points.Add(new Point(10, 130));
		lines1.Points.Add(new Point(130, 130));
		lines1.Points.Add(new Point(130, 90));
		lines1.Points.Add(new Point(10, 90));

		Polygon lines2 = new Polygon();
		lines2.Stroke = Brushes.Black;
		lines2.Fill = Brushes.Red;
		lines2.FillRule = FillRule.Nonzero;
		foreach(Point pt in lines1.Points) {
			lines2.Points.Add(new Point(pt.X + 250, pt.Y));
		}

		Canvas canvas = new Canvas();
		canvas.Children.Add(lines1);
		canvas.Children.Add(lines2);

		Window wnd = new Window();
		wnd.Content = canvas;

		Application app = new Application();
		app.Run(wnd);
	}
}
実行結果
コード3 実行結果

コード3は、図1の形を用いて EvenOdd と Nonzero のそれぞれの塗りつぶしルールで描画した多角形の例です。左側の多角形が、Polygon オブジェクトに規定で設定されている EvenOdd による描画、右の多角形が Nonzero を明示的に設定して描画した結果です。