ゲームエフェクトデザイナーのブログ | A Real-Time VFX Artist's Blog

About Making Materials on UE, Making Tools with C#, etc

C# 画像に選択範囲のような枠線を引く

前回の記事のつづき。
effect.hatenablog.com

よくある「選択範囲」の描画を実装してみた際のメモです。
ラバーバンドと呼ばれるもののようです。
Shiftキーを押している場合は正方形に補正されるようにしています。

f:id:moko_03_25:20200308052820g:plain

考え方としては、カーソルの開始位置と終了位置から4点の座標を出して「DrawLine」関数を4回実行して矩形の破線(枠線)を描画しています。
ボタンでライン描画と矩形ライン描画を切り替えられるようにしているためソースコードが冗長になっていますが、貼り付けておきたいと思います。

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace SampleRubberBand
{
	public partial class Form1 : Form
	{
		public Form1()
		{
			InitializeComponent();
		}

		private void Form1_Load(object sender, EventArgs e)
		{
			// 画像ファイルからImageを作成
			Image img = Image.FromFile("sample.png");

			// 画像を表示
			pictureBox1.Image = img;

			// 画像をバックアップしておく
			backupImage = new Bitmap(img);
		}

		// 変数
		bool quadMode = false;

		Point startPoint;
		Point endPoint;

		Color lineColer = Color.Red;
		int lineBorder = 4;

		Bitmap backupImage;
		Graphics g;
		Pen linePen;

		// マウスダウン時
		private void PictureBox1_MouseDown(object sender, MouseEventArgs e)
		{
			// 座標を保存
			startPoint.X = cursorPos().X;
			startPoint.Y = cursorPos().Y;
		}

		// マウスドラッグ中
		private void PictureBox1_MouseMove(object sender, MouseEventArgs e)
		{
			// ライン描画の場合
			if (quadMode == false)
			{
				// マウスの左ボタンが押されている場合のみ処理
				if ((Control.MouseButtons & MouseButtons.Left) == MouseButtons.Left)
				{
					// Shiftキーが押されていれば直線にする
					if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift)
					{
						// 比較のためにXとYの移動距離を算出
						int checkPosX = Math.Abs(cursorPos().X - startPoint.X);
						int checkPosY = Math.Abs(cursorPos().Y - startPoint.Y);

						// 角度を算出
						double d = 0.0;

						if (checkPosX != 0 && checkPosY != 0) // 0除算対策
						{
							// 角度を求める
							d = Math.Atan2(checkPosY, checkPosX);

							// ラジアンから度数に変換
							d = d * 180 / Math.PI;

							// ラベルに表示
							label2.Text = d.ToString();
						}

						// XとYのどちらが長いか比較する
						if (checkPosX >= checkPosY) // 横長の場合
						{
							if (40.0 < d && d < 50.0)
							{
								// 座標を取得
								// 開始位置に対して現在位置が正負どちらか判定して処理
								endPoint.X = cursorPos().X;
								endPoint.Y = startPoint.Y +
									(Math.Sign(cursorPos().Y - startPoint.Y) * checkPosX);
							}
							else
							{
								// 座標を取得
								endPoint.X = cursorPos().X;
								endPoint.Y = startPoint.Y;
							}
						}
						else // 縦長の場合
						{
							if (40.0 < d && d < 50.0)
							{
								// 座標を取得
								// 開始位置に対して現在位置が正負どちらか判定して処理
								endPoint.X = startPoint.X +
									(Math.Sign(cursorPos().X - startPoint.X) * checkPosY);
								endPoint.Y = cursorPos().Y;
							}
							else
							{
								// 座標を取得
								endPoint.X = startPoint.X;
								endPoint.Y = cursorPos().Y;
							}
						}
					}
					else
					{
						// 座標を取得
						endPoint.X = cursorPos().X;
						endPoint.Y = cursorPos().Y;
					}

					// 描画
					DrawLine(startPoint, endPoint);
				}
			}
			// 矩形ライン描画の場合
			else
			{
				// マウスの左ボタンが押されている場合のみ処理
				if ((Control.MouseButtons & MouseButtons.Left) == MouseButtons.Left)
				{
					// Shiftキーが押されていれば直線にする
					if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift)
					{
						// 比較のためにXとYの移動距離を算出
						int checkPosX = Math.Abs(cursorPos().X - startPoint.X);
						int checkPosY = Math.Abs(cursorPos().Y - startPoint.Y);

						// ラベルに表示
						label2.Text = "0".ToString();

						// XとYのどちらが長いか比較する
						if (checkPosX >= checkPosY) // 横長の場合
						{
							// 座標を取得
							// 開始位置に対して現在位置が正負どちらか判定して処理
							endPoint.X = cursorPos().X;
							endPoint.Y = startPoint.Y +
								(Math.Sign(cursorPos().Y - startPoint.Y) * checkPosX);
						}
						else // 縦長の場合
						{
							// 座標を取得
							// 開始位置に対して現在位置が正負どちらか判定して処理
							endPoint.X = startPoint.X +
								(Math.Sign(cursorPos().X - startPoint.X) * checkPosY);
							endPoint.Y = cursorPos().Y;
						}
					}
					else
					{
						// 座標を取得
						endPoint.X = cursorPos().X;
						endPoint.Y = cursorPos().Y;
					}

					// 描画
					Point p0 = new Point(startPoint.X, startPoint.Y);
					Point p1 = new Point(endPoint.X, startPoint.Y);
					Point p2 = new Point(startPoint.X, endPoint.Y);
					Point p3 = new Point(endPoint.X, endPoint.Y);
					DrawQuadLine(p0, p1, p2, p3);
				}
			}
		}

		// マウスアップ時
		private void PictureBox1_MouseUp(object sender, MouseEventArgs e)
		{
			// リソースを解放
			linePen.Dispose();
			g.Dispose();
		}

		// カーソル位置を取得
		private Point cursorPos()
		{
			// 画面座標でカーソルの位置を取得
			Point p = Cursor.Position;
			// 画面座標からコントロール上の座標に変換
			Point cp = this.PointToClient(p);

			return cp;
		}

		// ラインを描画
		private void DrawLine(Point p0, Point p1)
		{
			// 画像のバックアップを取得
			Bitmap canvasBase = new Bitmap(pictureBox1.Image);

			// 描画するImageオブジェクトを作成
			// サイズだけ指定すると無色透明のキャンバスになる
			Bitmap canvas = new Bitmap(pictureBox1.Width, pictureBox1.Height);
			//ImageオブジェクトのGraphicsオブジェクトを作成する
			g = Graphics.FromImage(canvas);

			// Penオブジェクトの作成
			linePen = new Pen(lineColer, lineBorder);

			// 先にバックアップしていた画像で塗り潰す
			g.DrawImage(backupImage, 0, 0);

			// スタイルを指定
			linePen.DashStyle = DashStyle.Solid;
			// ラインを描画
			g.DrawLine(linePen, p0.X, p0.Y, p1.X, p1.Y);

			// PictureBox1に表示
			pictureBox1.Image = canvas;
		}

		// 矩形ラインを描画
		private void DrawQuadLine(Point p0, Point p1, Point p2, Point p3)
		{
			// 画像のバックアップを取得
			Bitmap canvasBase = new Bitmap(pictureBox1.Image);

			// 描画するImageオブジェクトを作成
			// サイズだけ指定すると無色透明のキャンバスになる
			Bitmap canvas = new Bitmap(pictureBox1.Width, pictureBox1.Height);
			//ImageオブジェクトのGraphicsオブジェクトを作成する
			g = Graphics.FromImage(canvas);

			// Penオブジェクトの作成
			linePen = new Pen(lineColer, lineBorder);

			// 先にバックアップしていた画像で塗り潰す
			g.DrawImage(backupImage, 0, 0);

			// スタイルを指定
			linePen.DashStyle = DashStyle.Dot;
			// ラインを描画
			g.DrawLine(linePen, p0, p1); // 上辺
			g.DrawLine(linePen, p2, p3); // 底辺
			g.DrawLine(linePen, p0, p2); // 左辺
			g.DrawLine(linePen, p1, p3); // 右辺

			// PictureBox1に表示
			pictureBox1.Image = canvas;
		}

		private void BtnLine_Click(object sender, EventArgs e)
		{
			btnLine.BackColor = Color.YellowGreen;
			btnQuad.BackColor = Color.FromArgb(127, 127, 127);

			quadMode = false;
		}

		private void BtnQuad_Click(object sender, EventArgs e)
		{
			btnLine.BackColor = Color.FromArgb(127, 127, 127);
			btnQuad.BackColor = Color.YellowGreen;

			quadMode = true;
		}
	}
}