WisdomSoft - for your serial experiences.

2.9 座標変換

GDI+ で描画する図形は、事前にデバイスに設定されている行列を用いて座標変換できます。

2.9.1 ワールド変換

これまでは、フォームのクライアント領域の左上隅を原点 (0, 0) とする座標を使ってきましたが、座標の原点を変更することができます。例えば数学的な処理を行うプログラムを開発するときには画面中央を原点とした方が都合が良いかもしれません。または、座標の原点を変更することでグラフィックス出力処理全体の位置を平行移動させるということもできます。

GDI+ には、ワールド座標空間ページ座標空間という考え方があります。Graphics クラスの draw~() メソッドや fill~() メソッドで設定する座標値はワールド座標空間です。Graphics クラスは図形をデバイスに描画するとき、Graphics オブジェクトの設定に基づいてワールド座標をページ座標に変換します。この処理をワールド変換と呼びます。

ページ座標空間の原点は、常にクライアント領域の左上隅です。ワールド変換が行われるときに適切な座標に変換することによって、例えばワールド座標空間で指定した原点 (0, 0) をページ座標空間上の (100, 50) から描画することができます。

ワールド変換の平行移動を行うには Graphics クラスの TranslateTransform() メソッドを使います。

Graphics クラス TranslateTransform() メソッド
public void TranslateTransform(float dx, float dy)

dx にはX 座標、dy には Y 座標の平行移動分の距離を設定します。例えばワールド座標 (0, 0) をページ座標 (100, 50) に変換するには TranslateTransform(100, 50) を追加します。

コード1
using System.Drawing;
using System.Windows.Forms;

public class Test : Form
{
	protected override void OnPaint(PaintEventArgs e) 
	{
		base.OnPaint(e);
		Brush brush = new SolidBrush(Color.Black);
		e.Graphics.FillRectangle(brush, 0, 0, 200, 100);

		brush = new SolidBrush(Color.Blue);
		e.Graphics.TranslateTransform(100F, 50F);
		e.Graphics.FillRectangle(brush, 0, 0, 200, 100);
	}
	static void Main() 
	{
		Application.Run(new Test());
	}
}
実行結果
コード1 実行結果

コード1では2回 FillRectangle() メソッドを呼び出しています。両方とも座標とサイズの設定が同じであるにもかかわらず、実行結果を見ると描画されている位置が異なっています。これは、2回目の FillRectangle() メソッドの呼び出しの前に TranslateTransform() メソッドでワールド変換に平行移動を追加したためです。TranslateTransform() の設定によって、ワールド座標 (0, 0) はワールド変換によってページ座標 (100, 50) に変換されています。

2.9.2 変換行列

ワールド変換は Graphics オブジェクトの Transform プロパティに設定されている行列を使って行われています。TranslateTransform() を使わなくても、任意の行列を設定することで平行移動に加えて伸縮や回転を表現できます。

Graphics クラス Transform プロパティ
public Matrix Transform { get; set; }

行列は System.Drawing.Drawing2D.Matrix クラスを使って表現します。Matrix クラスは 3 × 3 の行列を提供するクラスです。

System.Drawing.Drawing2D.Matrix クラス
System.Object
    System.MarshalByRefObject
        System.Drawing.Drawing2D.Matrix
public sealed class Matrix : MarshalByRefObject, IDisposable

Matrix クラスは伸縮、回転、平行移動を行うメソッド公開しているため、行列演算の数学的な原理を理解する必要はありません。パラメータを受け取らない Matrix() コンストラクタを呼び出せば、行列は変換を行わない単位行列(整数における 1 に相当する値)を生成します。

伸縮を行うには Matrix クラスの Scale() メソッドを、回転を行うには Rotate() メソッドを、平行移動を行うには Translate() メソッドを使います。

Matrix クラス Scale() メソッド
public void Scale(float scaleX, float scaleY);

scaleX パラメータには X 軸方向に伸縮する値、scaleY パラメータには Y 軸方向に伸縮する値を指定します。 

Matrix クラス Rotate() メソッド
public void Rotate(float angle)

angle パラメータに回転角度を指定します。

Matrix クラス Translate() メソッド
public void Translate(float offsetX, float offsetY)

offsetX パラメータには平行移動する X 軸の値、offsetY パラメータには平行移動する Y 軸の値を指定します。

これらのメソッドを組み合わせて、伸縮と回転、平行移動のワールド変換を同時に行うことができます。

コード2
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

public class Test : Form
{
	protected override void OnPaint(PaintEventArgs e) 
	{
		base.OnPaint(e);
		Rectangle rect = new Rectangle(0, 0, 200, 100);
		Brush brush = new LinearGradientBrush(
			rect, Color.Black, Color.Red, LinearGradientMode.ForwardDiagonal);

		Matrix matrix = new Matrix();
		matrix.Scale(2.0F, 1.5F);
		matrix.Rotate(30F);
		matrix.Translate(80F, 0F);
		e.Graphics.Transform = matrix;
		e.Graphics.FillRectangle(brush, rect);
	}
	static void Main() 
	{
		Application.Run(new Test());
	}
}
実行結果
コード2 実行結果

コード2は、Matrix オブジェクトを生成し、伸縮、回転、平行移動を追加して Graphics オブジェクトの Transform プロパティに設定しています。ワールド座標 (0, 0) から幅 200、高さ 100 の矩形を単純に描画しているだけですが、ワールド変換によって矩形が回転し、サイズと表示位置も変化しています。

2.9.3 デバイス座標空間

これまで、すべての座標や幅、サイズはピクセル単位で設定してきました。しかし、アプリケーションの種類によってはセンチメートルやインチで数えたい場合があるかもしれません。例えば印刷処理や、画面サイズを測るプログラムなどの場合です。

ワールド座標単位を変更するには Graphics クラスの PageUnit プロパティを使います。PageUnit は長さの単位を設定するプロパティです。GDI+ では、ワールド座標空間とページ座標空間に加えてデバイス座標空間が存在します。デバイス座標空間は、ページ座標空間を変換して最終的にデバイスに配置される座標です。

Graphics クラス PageUnit プロパティ
public GraphicsUnit PageUnit { get; set; }

ページ座標をデバイス座標に変換する処理をページ変換と呼びます。ワールド座標で指定した値は PageUnit に設定されている単位に基づいて適切なデバイス座標に変換されます。

PageUnit プロパティには System.Drawing.GraphicsUnit 列挙体のメンバのいずれかを指定します。

System.Drawing.GraphicsUnit 列挙体
[Serializable]
public enum GraphicsUnit

ピクセル単位は Pixel、インチ単位は Inch、ミリメートル単位は Millimeter となります。

コード3
using System.Drawing;
using System.Windows.Forms;

public class Test : Form
{
	protected override void OnPaint(PaintEventArgs e) 
	{
		base.OnPaint(e);
		Pen pen = new Pen(Color.Red);
		Rectangle rect = new Rectangle(10, 10, 200 , 100);
		e.Graphics.DrawRectangle(pen , rect);

		pen.Color = Color.Blue;
		e.Graphics.PageUnit = GraphicsUnit.Document;
		e.Graphics.DrawRectangle(pen, rect);

		pen.Color = Color.Green;
		e.Graphics.PageUnit = GraphicsUnit.Millimeter;
		e.Graphics.DrawRectangle(pen, rect);
	}
	static void Main() 
	{
		Application.Run(new Test());
	}
}
実行結果
コード3 実行結果

コード3では、ワールド座標 (10, 10) から幅 200、高さ 100 の矩形を描画しています。しかし、DrawRectangle() メソッド呼び出しの前に PageUnit プロパティの値を変更しているため、単位が異なっています。赤い矩形はピクセル、青い矩形はドキュメント単位(1/300インチ)、緑色の矩形はミリメートル単位となります。