Contents
画像全体の反転・回転であれば簡単
今回は左右上下反転と回転をやります。といってもBitmapクラスのRotateFlipメソッドを使うだけなので簡単です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
public partial class Form1 : Form { private void ReverseLeftRightMenuItem_Click(object sender, EventArgs e) { bool ret = userControlImage1.RotateFlip(RotateFlipType.RotateNoneFlipX); if(!ret) MessageBox.Show("画像が読み込まれていません", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } private void ReverseUpperLowerMenuItem_Click(object sender, EventArgs e) { bool ret = userControlImage1.RotateFlip(RotateFlipType.RotateNoneFlipY); if(!ret) MessageBox.Show("画像が読み込まれていません", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } private void Rotate90MenuItem_Click(object sender, EventArgs e) { bool ret = userControlImage1.RotateFlip(RotateFlipType.Rotate90FlipNone); if(!ret) MessageBox.Show("画像が読み込まれていません", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } private void Rotate180MenuItem_Click(object sender, EventArgs e) { bool ret = userControlImage1.RotateFlip(RotateFlipType.Rotate180FlipNone); if(!ret) MessageBox.Show("画像が読み込まれていません", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } private void Rotate270MenuItem_Click(object sender, EventArgs e) { bool ret = userControlImage1.RotateFlip(RotateFlipType.Rotate270FlipNone); if(!ret) MessageBox.Show("画像が読み込まれていません", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } } |
そしてUserControlImageクラス側ではこのような処理をおこないます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public partial class UserControlImage : UserControl { public bool RotateFlip(RotateFlipType rotateFlipType) { if(Bitmap == null) return false; if(BitmapRectangle != null) UniteBitmapRectangle(new Point()); // bmpとは別にnewBitmapを作成する Bitmap newBitmap = new Bitmap(Bitmap); newBitmap.RotateFlip(rotateFlipType); Bitmap = newBitmap; ShowBitmap(Bitmap); return true; } } |
範囲選択されている部分に対する処理
これではちょっと早く終わりすぎなので、範囲選択されている部分のみを左右上下反転したり回転させる方法を考えてみることにします。
まず範囲選択はユーザーコントロールをドラッグ&ドロップすることでおこなうものとします。
今後範囲指定された部分を移動させることも考えています。そのため選択されている矩形とイメージを両方セットで扱えるクラスを作成しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class BitmapRectangle { public BitmapRectangle(Bitmap bitmap, Rectangle rectangle) { Bitmap = bitmap; Rectangle = rectangle; } public Bitmap Bitmap { set; get; } public Rectangle Rectangle { private set; get; } } |
イベントハンドラの追加
ユーザーコントロール上にあるピクチャーボックスをドラッグするのでイベントハンドラを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public partial class UserControlImage : UserControl { public UserControlImage() { InitializeComponent(); this.vScrollBar1.Scroll += VScrollBar1_Scroll; this.hScrollBar1.Scroll += HScrollBar1_Scroll; // 以下の3つを新しく追加 this.pictureBox1.MouseDown += PictureBox1_MouseDown; this.pictureBox1.MouseUp += PictureBox1_MouseUp; this.pictureBox1.MouseMove += PictureBox1_MouseMove; } } |
ドラッグの開始点と終了点から選択範囲を求める
クリックされると範囲選択の開始、マウスボタンが離されると範囲選択の終了です。startPointはマウスがクリックされた座標、endPointはマウスが離された座標です。そして座標はビットマップ上の座標です。
範囲選択が開始された点と終了した点から範囲選択されている矩形を求めることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public partial class UserControlImage : UserControl { // フィールド変数 bool isMouseDown = false; Point startPoint = new Point(-1, -1); Point endPoint = new Point(-1, -1); public BitmapRectangle BitmapRectangle { set; get; } = null; } |
ビットマップ上でクリックされたのか?
またクリックされる場所はピクチャーボックス内であっても画像がある場所とは限りません。そこでビットマップ上でクリックされたかどうかも調べる必要があります。
OnMouseDownOutOfBitmapメソッドはビットマップが存在しない場所でクリックされたときに呼び出されます。このときの処理はBitmapプロパティで取得されるビットマップを再描画するだけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public partial class UserControlImage : UserControl { private void PictureBox1_MouseDown(object sender, MouseEventArgs e) { if(Bitmap == null) return; // e.X、e.Yはピクチャーボックス上の座標である。 // ビットマップ上の座標はスクロールバーの値を加算すればよい // 範囲外におけるクリック if(e.X + ScrollBarPosX >= Bitmap.Width || e.Y + ScrollBarPosY >= Bitmap.Height) { OnMouseDownOutOfBitmap(); return; } // すでに範囲選択されているかどうか? // されているなら解除、されていないなら選択の処理を開始する if(BitmapRectangle == null) OnMouseDownForSelectRectangle(new Point(e.X + ScrollBarPosX, e.Y + ScrollBarPosY)); else OnMouseDownOutOfSelection(new Point(e.X + ScrollBarPosX, e.Y + ScrollBarPosY)); } void OnMouseDownOutOfBitmap() { startPoint = new Point(-1, -1); ShowBitmap(Bitmap); } } |
範囲選択の開始点を取得
BitmapRectangleプロパティが nullのときにマウスがクリックされたということは範囲選択の開始点が指定されたということです。そこで自作メソッドのOnMouseDownForSelectRectangleメソッドを呼び出します。
マウスキャプチャして現在選択されているビットマップ上の座標をフィールド変数startPointに保存しています。またマウスボタンが押されていることがわかるようにフィールド変数isMouseDownをtrueにしています。
1 2 3 4 5 6 7 8 9 |
public partial class UserControlImage : UserControl { void OnMouseDownForSelectRectangle(Point bitmapPoint) { isMouseDown = true; pictureBox1.Capture = true; startPoint = new Point(bitmapPoint.X, bitmapPoint.Y); } } |
選択の解除
BitmapRectangleプロパティがnullではないときにマウスがクリックされたということは範囲選択された部分とは別の場所でマウスがクリックされたということです。この場合はOnMouseDownOutOfSelectionメソッドを呼び出して範囲選択を解除します。
startPointとendPointに(-1, -1)をセットしているのは、矩形を求めるかどうかを判断する目印にするためです。
1 2 3 4 5 6 7 8 9 10 11 |
public partial class UserControlImage : UserControl { void OnMouseDownOutOfSelection(Point bitmapPoint) { startPoint = new Point(-1, -1); endPoint = new Point(-1, -1); UniteBitmapRectangle(bitmapPoint); ShowBitmap(Bitmap); } } |
ドラッグ時の処理
次にマウスが移動したときの処理を考えます。範囲選択のメニューが選択されているときにマウスボタンがおされた状態でマウスを動かすと自作メソッドのOnMouseMoveForSelectRectangleがよばれます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class UserControlImage : UserControl { private void PictureBox1_MouseMove(object sender, MouseEventArgs e) { if(Bitmap == null) return; if(isMouseDown) { if(EditMode == EditMode.Selection) { OnMouseMoveForSelectRectangle(new Point(e.X + ScrollBarPosX, e.Y + ScrollBarPosY)); return; } } } } |
境界線を描画するDrawBoderRectangleメソッド
OnMouseMoveForSelectRectangleメソッドによってDrawBoderRectangleメソッドがよばれます。これは選択されている矩形を求めて境界線が描画されたビットマップを得るメソッドです。戻り値をShowBitmapメソッドに渡すことで境界線が表示されます。
GetRectangleメソッドはフィールド変数startPointとendPointから矩形を求めます。点の座標が(-1,-1)であれば空の矩形を返します。このときは境界線は表示されません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
public partial class UserControlImage : UserControl { void OnMouseMoveForSelectRectangle(Point bitmapPoint) { endPoint = bitmapPoint; Bitmap bitmap = DrawBoderRectangle(Bitmap); ShowBitmap(bitmap); } Bitmap DrawBoderRectangle(Bitmap bitmap) { Rectangle rect; if(BitmapRectangle == null) { rect = GetRectangle(startPoint, endPoint); } else { rect = BitmapRectangle.Rectangle; } Bitmap newBitmap = new Bitmap(bitmap); Graphics g = Graphics.FromImage(newBitmap); g.DrawRectangle(new Pen(GetBoderBrush()), rect); g.Dispose(); return newBitmap; } // 境界線につかうブラシ Brush GetBoderBrush() { return new System.Drawing.Drawing2D.HatchBrush( System.Drawing.Drawing2D.HatchStyle.BackwardDiagonal, Color.Black, Color.FromArgb(255, 128, 128, 128)); } } |
2点から矩形を求める
GetRectangleメソッドは2点から矩形を求めるメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public partial class UserControlImage : UserControl { Rectangle GetRectangle(Point pt1, Point pt2) { if(pt1.X == -1 || pt2.X == -1) return new Rectangle(); if(pt1.X < pt2.X) { if(pt1.Y < pt2.Y) return new Rectangle(pt1, new Size(pt2.X - pt1.X, pt2.Y - pt1.Y)); else return new Rectangle(new Point(pt1.X, pt2.Y), new Size(pt2.X - pt1.X, -(pt2.Y - pt1.Y))); } else { if(pt1.Y < pt2.Y) return new Rectangle(new Point(pt2.X, pt1.Y), new Size(pt1.X - pt2.X, pt2.Y - pt1.Y)); else return new Rectangle(pt2, new Size(pt1.X - pt2.X, pt1.Y - pt2.Y)); } } } |
ドラッグが終わったときの処理
マウスボタンが離されたときはどうすればいいのでしょうか? isMouseDownをfalseにします。そして得られた矩形と矩形内のビットマップからBitmapRectangleオブジェクトを生成してプロパティをセットします。
これで範囲選択ができました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
public partial class UserControlImage : UserControl { private void PictureBox1_MouseUp(object sender, MouseEventArgs e) { if(Bitmap == null) return; if(isMouseDown) { if(EditMode == EditMode.Selection) OnMouseUpForSelectRectangle(); } } void OnMouseUpForSelectRectangle() { isMouseDown = false; Rectangle rect = GetRectangle(startPoint, endPoint); if(rect.IsEmpty || rect.Width == 0 || rect.Height == 0) { startPoint = new Point(-1, -1); endPoint = new Point(-1, -1); return; } Bitmap bitmap = new Bitmap(rect.Width, rect.Height); Graphics g = Graphics.FromImage(bitmap); Rectangle destRect = new Rectangle(0, 0, rect.Width, rect.Height); g.DrawImage(Bitmap, destRect, rect, GraphicsUnit.Pixel); g.Dispose(); BitmapRectangle = new BitmapRectangle(new Bitmap(bitmap), rect); } } |
選択範囲ができたら選択部分を反転させる処理をおこないます。
メニューの追加
そのまえにデザイナをつかってメニューを追加します。
メニューが選択されたらUserControlImageクラスのSelectionRotateFlipメソッドを呼び出します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
public partial class Form1 : Form { private void SelectionReverseLeftRightMenuItem_Click(object sender, EventArgs e) { bool ret = userControlImage1.SelectionRotateFlip(RotateFlipType.RotateNoneFlipX); if(!ret) MessageBox.Show("画像が読み込まれていないか範囲選択がされていません", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } private void SelectionReverseUpperLowerMenuItem_Click(object sender, EventArgs e) { bool ret = userControlImage1.SelectionRotateFlip(RotateFlipType.RotateNoneFlipY); if(!ret) MessageBox.Show("画像が読み込まれていないか範囲選択がされていません", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } private void SelectionRotate90MenuItem_Click(object sender, EventArgs e) { bool ret = userControlImage1.SelectionRotateFlip(RotateFlipType.Rotate90FlipNone); if(!ret) MessageBox.Show("画像が読み込まれていないか範囲選択がされていません", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } private void SelectionRotate180MenuItem_Click(object sender, EventArgs e) { bool ret = userControlImage1.SelectionRotateFlip(RotateFlipType.Rotate180FlipNone); if(!ret) MessageBox.Show("画像が読み込まれていないか範囲選択がされていません", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } private void SelectionRotate270MenuItem_Click(object sender, EventArgs e) { bool ret = userControlImage1.SelectionRotateFlip(RotateFlipType.Rotate270FlipNone); if(!ret) MessageBox.Show("画像が読み込まれていないか範囲選択がされていません", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } } |
SelectionRotateFlipメソッド
UserControlImageクラスのSelectionRotateFlipメソッドは以下のとおりです。
BitmapRectangleがnullでないことを確認してからBitmapRectangle.BitmapをBitmap.RotateFlipメソッドで変更します。そして得られたビットマップと元からあるビットマップを合成し、これをBitmapプロパティにセットします。最後にShowBitmapメソッドを呼び出して表示させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public partial class UserControlImage : UserControl { public bool SelectionRotateFlip(RotateFlipType rotateFlipType) { if(Bitmap == null) return false; if(BitmapRectangle == null) return false; Bitmap bitmap = BitmapRectangle.Bitmap; Rectangle rect = BitmapRectangle.Rectangle; bitmap.RotateFlip(rotateFlipType); Bitmap bitmap1 = new Bitmap(bitmap); // Bitmapとは別にnewBitmapを作成する Bitmap newBitmap = new Bitmap(Bitmap); Graphics g = Graphics.FromImage(newBitmap); Rectangle srcRect = new Rectangle(0, 0, bitmap1.Width, bitmap1.Height); g.DrawImage(bitmap1, rect, srcRect, GraphicsUnit.Pixel); g.Dispose(); ShowBitmap(DrawBoderRectangle(Bitmap)); return true; } } |