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

ワールド変換により画像を平行移動、拡大、縮小、回転して表示する

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

画像を拡大、縮小(スケーリング)して描画する」、「画像を傾けて表示する」にて画像を拡大、縮小、回転して表示する方法を紹介しましたが、ここでは「ワールド変換行列」を使う方法を紹介します。

ワールド変換行列はMatrixオブジェクトで表わされ、Matrixオブジェクトには2x2の線形変換のための行列と、1x2の平行移動のための行列が組み合わさった、3x3のアフィン行列が格納されています(詳しくはMSDNの「変換の行列表現」をご覧ください)。あるGraphicsオブジェクトで描画されるすべての画像や図形に適用する変換(グローバル変換)のためのMatrixオブジェクトは、GraphicsオブジェクトのTransformプロパティで取得、設定が出来ます。

なにやら難しいことになってきましたが、平行移動、スケーリング(拡大、縮小)、回転に関しては簡単に実現する方法が用意されていますので、ここではそれらを使うことにします。

あるGraphicsオブジェクトのワールド変換に平行移動を適用するにはGraphics.TranslateTransformメソッドを、スケーリングを適用するにはGraphics.ScaleTransformメソッドを、回転を適用するにはGraphics.RotateTransformメソッドをそれぞれ使用します。

次にこれらの変換を利用して画像を描画する例を示します。

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

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

'画像を読み込む
Dim img As Image = Image.FromFile("test.gif")

'普通に画像を描画
g.DrawImage(img, New Rectangle(0, 0, img.Width, img.Height))

'ワールド変換行列を単位行列にリセット
g.ResetTransform()
'ワールド変換行列を下に10平行移動する
g.TranslateTransform(0, img.Height + 10)
'画像を描画
g.DrawImage(img, New Rectangle(0, 0, img.Width, img.Height))

'ワールド変換行列を単位行列にリセット
g.ResetTransform()
'ワールド変換行列にスケーリング操作を適用し、2倍に拡大する
g.ScaleTransform(2.0F, 2.0F)
'画像を描画
g.DrawImage(img, New Rectangle(img.Width, 0, img.Width, img.Height))

'ワールド変換行列を単位行列にリセット
g.ResetTransform()
'ワールド変換行列を45度回転する
g.RotateTransform(45.0F)
'画像を描画
g.DrawImage(img, New Rectangle(img.Width, img.Height, img.Width, img.Height))

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

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

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

//画像を読み込む
Image img = Image.FromFile("test.gif");

//普通に画像を描画
g.DrawImage(img, new Rectangle(0, 0, img.Width, img.Height));

//ワールド変換行列を単位行列にリセット
g.ResetTransform();
//ワールド変換行列を下に10平行移動する
g.TranslateTransform(0, img.Height + 10);
//画像を描画
g.DrawImage(img, new Rectangle(0, 0, img.Width, img.Height));

//ワールド変換行列を単位行列にリセット
g.ResetTransform();
//ワールド変換行列にスケーリング操作を適用し、2倍に拡大する
g.ScaleTransform(2F, 2F);
//画像を描画
g.DrawImage(img, new Rectangle(img.Width, 0, img.Width, img.Height));

//ワールド変換行列を単位行列にリセット
g.ResetTransform();
//ワールド変換行列を45度回転する
g.RotateTransform(45F);
//画像を描画
g.DrawImage(img, new Rectangle(img.Width, img.Height, img.Width, img.Height));

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

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

画像を拡大、縮小、回転のためにこのようなワールド変換を行うとき、画像だけが拡大、縮小、回転するわけではないことに注意してください。つまり画像の表示位置に関しても座標が変換されるので、そのことも考慮して描画位置を指定する必要があります。

複合変換

上記の例ではそれぞれの変換を独立して行うため、ワールド変換を指定する前にResetTransformメソッドにより、ワールド変換行列を単位行列にリセットしています。もしこのリセットを行わなければ、適用が指定されるすべての変換は次々と追加され、連続する複数の変換(複合変換)となります。

次にこのことを確認するため、リセットせずに平行移動後回転のワールド変換を設定してみます。

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

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

'画像を読み込む
Dim img As Image = Image.FromFile("test.gif")

'普通に画像を描画
g.DrawImage(img, New Rectangle(0, 0, img.Width, img.Height))

'ワールド変換行列を右に平行移動する
g.TranslateTransform(120, 0)
'画像を描画
g.DrawImage(img, New Rectangle(0, 0, img.Width, img.Height))

'ワールド変換行列を45度回転し、追加する
g.RotateTransform(45.0F)
'画像を描画
g.DrawImage(img, New Rectangle(0, 0, img.Width, img.Height))

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

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

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

//画像を読み込む
Image img = Image.FromFile("test.gif");

//普通に画像を描画
g.DrawImage(img, new Rectangle(0, 0, img.Width, img.Height));

//ワールド変換行列を右に平行移動する
g.TranslateTransform(120, 0);
//画像を描画
g.DrawImage(img, new Rectangle(0, 0, img.Width, img.Height));

//ワールド変換行列を45度回転し、追加する
g.RotateTransform(45F);
//画像を描画
g.DrawImage(img, new Rectangle(0, 0, img.Width, img.Height));

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

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

この結果を見ると、回転後に平行移動していることが分かります。つまり、新しい変換は前に追加されるということが分かります。新しい変換を後ろに追加するために、MatrixOrder列挙体を指定することも出来ます。次の例では回転の操作を平行移動の後ろに追加してみましょう。

VB.NET
コードを隠すコードを選択
'Imports System.Drawing
'Imports System.Drawing.Drawing2D
'がソースファイルの一番上に書かれているものとする

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

'画像を読み込む
Dim img As Image = Image.FromFile("test.gif")

'普通に画像を描画
g.DrawImage(img, New Rectangle(0, 0, img.Width, img.Height))

'ワールド変換行列を右に平行移動する
g.TranslateTransform(120, 0)
'画像を描画
g.DrawImage(img, New Rectangle(0, 0, img.Width, img.Height))

'ワールド変換行列を45度回転し、後ろに追加する
g.RotateTransform(45.0F, MatrixOrder.Append)
'画像を描画
g.DrawImage(img, New Rectangle(0, 0, img.Width, img.Height))

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

'PictureBox1に表示する
PictureBox1.Image = canvas
C#
コードを隠すコードを選択
//using System.Drawing;
//using System.Drawing.Drawing2D;
//がソースファイルの一番上に書かれているものとする

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

//画像を読み込む
Image img = Image.FromFile("test.gif");

//普通に画像を描画
g.DrawImage(img, new Rectangle(0, 0, img.Width, img.Height));

//ワールド変換行列を右に平行移動する
g.TranslateTransform(120, 0);
//画像を描画
g.DrawImage(img, new Rectangle(0, 0, img.Width, img.Height));

//ワールド変換行列を45度回転し、後ろに追加する
g.RotateTransform(45F ,MatrixOrder.Append);
//画像を描画
g.DrawImage(img, new Rectangle(0, 0, img.Width, img.Height));

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

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

平行移動後に回転されているのがお分かりいただけるでしょう。

これらの結果を見れば一目瞭然ですが、変換操作を後ろにするか前にするかという順番は場合によってはとても重要になります。このことに関してはMSDNの「変換順序が重要となる理由」にも詳しく書かれています。

  • 履歴:
  • 2012/8/1 表示する方法を、PictureBox.Imageプロパティを使った方法に変更。

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

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