では画像のなかに文字をいれることができるようになりましたが、文字の位置を変えることができません。そこで文字の位置を変更できるようにします。
選択範囲を移動させるためのBitmapRectangleプロパティ
そのためには領域が選択されている状態でドラッグするとその部分を移動できるようにすることが必要です。これまでBitmapRectangleプロパティを使ってきましたが、これは描画したものを移動させるためにつくられたものです。
移動しているものを再度固定させるのは範囲選択されている部分以外の部分がクリックされたときです。Windowsに標準搭載されているPaintも追加された矩形、楕円、文字などをドラッグすれば移動させることができ、それ以外の部分をクリックすると固定されます。
BitmapRectangleクラスにプロパティを追加する
そこで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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
public class BitmapRectangle { public BitmapRectangle(Bitmap bitmap, Rectangle rectangle) { Bitmap = bitmap; Rectangle = rectangle; OldBitmap = new Bitmap(bitmap); OldX = rectangle.X; OldY = rectangle.Y; OldWidth = rectangle.Width; OldHeight = rectangle.Height; } public void UpdatePosition() { OldX = X; OldY = Y; OldWidth = Width; OldHeight = Height; } public void Dispose() { Bitmap.Dispose(); } public Bitmap Bitmap { set; get; } public Bitmap OldBitmap { private set; get; } = null; public int OldX { private set; get; } public int OldY { private set; get; } public int OldWidth { private set; get; } public int OldHeight { private set; get; } public Rectangle Rectangle { private set; get; } public int X { set { Rectangle rect = Rectangle; rect.X = value; Rectangle = rect; } get { return Rectangle.X; } } public int Y { set { Rectangle rect = Rectangle; rect.Y = value; Rectangle = rect; } get { return Rectangle.Y; } } public Point Location { set { Rectangle rect = Rectangle; rect.Location = value; Rectangle = rect; } get { return Rectangle.Location; } } } |
選択範囲の移動開始がおこなわれる条件とは?
UserControlImageがクリックされたらBitmapRectangleプロパティを調べます。もしnullであれば移動可能なものは存在しないということになります。
もしBitmapRectangle != nullであるならクリックされたのは矩形の内部かどうか調べます。矩形の内部であればドラッグによる移動がはじまると判断して、現在の矩形の位置とマウスクリックされた場所を調べます。ドラッグされている最中とこれが終わったときにマウス座標とクリックされた位置を比較すればどれだけ移動したのかがわかります。
イベントハンドラPictureBox1_MouseDownの修正
以下はこの処理に必要なフィールド変数と機能が追加されたイベントハンドラPictureBox1_MouseDownです。最後のほうにあるIsPointInRectangleメソッドはクリックされた場所が範囲選択されている部分なのかどうかを判定するものです。そして範囲選択されている部分がクリックされたときはOnMouseDownInBitmapRectangleメソッドを、範囲選択されていない部分であれば OnMouseDownOutOfSelectionメソッドを呼びます。
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
public partial class UserControlImage : UserControl { // BitmapRectangleを移動するためにマウスボタンが押されている bool isMouseDownForMove = false; // 移動前のBitmapRectangle座標 int rectangleMoveStartLocationX = 0; int rectangleMoveStartLocationY = 0; // BitmapRectangleを移動する前のマウス座標 int rectangleMoveStartMousePosX = 0; int rectangleMoveStartMousePosY = 0; private void PictureBox1_MouseDown(object sender, MouseEventArgs e) { if(Bitmap == null) return; // 範囲外におけるクリック if(e.X + ScrollBarPosX >= Bitmap.Width || e.Y + ScrollBarPosY >= Bitmap.Height) { OnMouseDownOutOfBitmap(); Console.WriteLine("OnMouseDownOutOfBitmap()"); return; } // クリックされた場所の色を通知する if(PictureBoxMouseDown != null) { Color color = Bitmap.GetPixel(e.X + ScrollBarPosX, e.Y + ScrollBarPosY); PictureBoxMouseDown(this, new PictureBox1MouseDownArgs(e.X + ScrollBarPosX, e.Y + ScrollBarPosY, color)); } if(BitmapRectangle == null) { if(EditMode == EditMode.Selection) OnMouseDownForSelectRectangle(new Point(e.X + ScrollBarPosX, e.Y + ScrollBarPosY)); if(EditMode == EditMode.DrawRectangle || EditMode == EditMode.FillRectangle || EditMode == EditMode.DrawEllipse || EditMode == EditMode.FillEllipse) OnMouseDownForDrawRectangle(new Point(e.X + ScrollBarPosX, e.Y + ScrollBarPosY)); if(EditMode == EditMode.DrawLine) OnMouseDownForDrawLine(new Point(e.X + ScrollBarPosX, e.Y + ScrollBarPosY)); if(EditMode == EditMode.FreeLine) OnMouseDownForDrawFreeLine(new Point(e.X + ScrollBarPosX, e.Y + ScrollBarPosY)); } else { if(IsPointInRectangle(new Point(e.X + ScrollBarPosX, e.Y + ScrollBarPosY), BitmapRectangle.Rectangle)) OnMouseDownInBitmapRectangle(new Point(e.X + ScrollBarPosX, e.Y + ScrollBarPosY)); else OnMouseDownOutOfSelection(new Point(e.X + ScrollBarPosX, e.Y + ScrollBarPosY)); } } } |
IsPointInRectangleメソッドは以下のようになっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public partial class UserControlImage : UserControl { bool IsPointInRectangle(Point pt, Rectangle rect) { if(pt.X < rect.Left) return false; if(pt.Y < rect.Top) return false; if(rect.Right < pt.X) return false; if(rect.Bottom < pt.Y) return false; return true; } } |
移動の開始を知る
OnMouseDownInBitmapRectangleメソッドはOnMouseDownForMoveSelectionメソッドを呼び出します。
OnMouseDownForMoveSelectionメソッドは移動のためにマウスボタンが押されていることを示すisMouseDownForMoveフラグをセットして現在のBitmapRectangleの位置とクリックされた座標(ビットマップ上の座標)をフィールド変数に保存します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public partial class UserControlImage : UserControl { void OnMouseDownInBitmapRectangle(Point bitmapPoint) { OnMouseDownForMoveSelection(bitmapPoint); } void OnMouseDownForMoveSelection(Point clickedBitmapPoint) { isMouseDownForMove = true; pictureBox1.Capture = true; rectangleMoveStartMousePosX = clickedBitmapPoint.X; rectangleMoveStartMousePosY = clickedBitmapPoint.Y; rectangleMoveStartLocationX = BitmapRectangle.X; rectangleMoveStartLocationY = BitmapRectangle.Y; } } |
移動元を白抜きにする
これは範囲選択するためのドラッグが終了したときに呼ばれるOnMouseUpForSelectRectangleメソッドですが、最後にCreateBlankBitmapメソッドを呼んでいます。
これは空白部分を挿入するためのものです。こうすることで移動している部分が元の位置から外れて移動しているようにみせかけることができます。
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 |
public partial class UserControlImage : UserControl { 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); // これを追加 if(blankBitmap == null) CreateBlankBitmap(BitmapRectangle.Rectangle); } } |
CreateBlankBitmapメソッドを示します。透明の部分は透明にして、それ以外の部分は白にしています。
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 |
public partial class UserControlImage : UserControl { BitmapRectangle blankBitmap = null; public void CreateBlankBitmap(Rectangle rect) { Bitmap bmp = Bitmap; if(blankBitmap != null) blankBitmap.Dispose(); Bitmap bitmap = new Bitmap(rect.Width, rect.Height); for(int x = 0; x < rect.Width; x++) { if(x + rect.X > bmp.Width - 1) continue; for(int y = 0; y < rect.Height; y++) { if(y + rect.Y > bmp.Height - 1) continue; Color color = bmp.GetPixel(x + rect.X, y + rect.Y); if(color == Color.FromArgb(0, 0, 0, 0)) bitmap.SetPixel(x, y, color); else bitmap.SetPixel(x, y, Color.White); } } blankBitmap = new BitmapRectangle(bitmap, rect); } } |
イベントハンドラPictureBox1_MouseMoveの修正
PictureBox1_MouseMoveメソッドはマウスがピクチャーボックスの上を移動したときに呼び出されるイベントハンドラです。ここに選択されている部分を移動させるための機能を追加しました。(OnMouseMoveForMoveSelectionメソッド)
それからドラッグされているときだけでなくマウスが通過しているだけでも処理をしています。範囲選択されている部分の上を通過するときはカーソルの形が変わります。ChangeCursorWhenMouseMoveOnSelectionメソッドはそのためのものです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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)); if(EditMode == EditMode.DrawRectangle || EditMode == EditMode.FillRectangle || EditMode == EditMode.DrawEllipse || EditMode == EditMode.FillEllipse) OnMouseMoveForDrawRectangle(new Point(e.X + ScrollBarPosX, e.Y + ScrollBarPosY)) if(EditMode == EditMode.DrawLine) OnMouseMoveForDrawLine(new Point(e.X + ScrollBarPosX, e.Y + ScrollBarPosY)); if(EditMode == EditMode.FreeLine) OnMouseMoveForDrawFreeLine(new Point(e.X + ScrollBarPosX, e.Y + ScrollBarPosY)); } else ChangeCursorWhenMouseMoveOnSelection(new Point(e.X + ScrollBarPosX, e.Y + ScrollBarPosY)); if(isMouseDownForMove) OnMouseMoveForMoveSelection(new Point(e.X + ScrollBarPosX, e.Y + ScrollBarPosY)); } } |
移動可能であればマウスポインタの形状を変更する
ChangeCursorWhenMouseMoveOnSelectionメソッドは以下のようになっています。BitmapRectangleプロパティがnullでないことを確認してからIsPointInRectangleメソッドをつかってマウスポインタの位置がBitmapRectangle.Rectangleの内部かどうかを調べています。
1 2 3 4 5 6 7 8 9 10 11 |
public partial class UserControlImage : UserControl { void ChangeCursorWhenMouseMoveOnSelection(Point bitmapPoint) { if(BitmapRectangle != null && IsPointInRectangle(new Point(bitmapPoint.X, bitmapPoint.Y), BitmapRectangle.Rectangle)) Cursor = Cursors.Hand; else Cursor = Cursors.Default; return; } } |
ドラッグされ選択範囲を移動させるOnMouseMoveForMoveSelectionメソッド
一方、ドラッグされているとき(= isMouseDownForMoveフラグがセットされているとき)に呼び出されるOnMouseMoveForMoveSelectionメソッドは以下のようになっています。
マウス座標がビットマップ上であればどこに対応するかを求めて、これとクリック時に保存しておいたフィールド変数rectangleMoveStartMousePosX、rectangleMoveStartMousePosYと比較してどれだけ移動したかを調べています。そしてBitmapのコピーとBitmapRectangle.Bitmapを適切な位置で合成します。
こうすることで選択領域が移動しているように見せかけています。
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 { void OnMouseMoveForMoveSelection(Point bitmapPoint) { int xMove = bitmapPoint.X - rectangleMoveStartMousePosX; int yMove = bitmapPoint.Y - rectangleMoveStartMousePosY; BitmapRectangle.X = rectangleMoveStartLocationX + xMove; BitmapRectangle.Y = rectangleMoveStartLocationY + yMove; Bitmap newBitmap = new Bitmap(Bitmap); Graphics g = Graphics.FromImage(newBitmap); if(blankBitmap != null) { g.DrawImage(blankBitmap.Bitmap, blankBitmap.Location); } g.DrawImage(BitmapRectangle.Bitmap, BitmapRectangle.Location); g.Dispose(); ShowBitmap(DrawBoderRectangle(newBitmap)); } } |
ドラッグによる移動が終了したときに呼び出されるOnMouseUpForMoveSelectionメソッド
最後にドラッグが終了したときの処理を示します。
移動のためのドラッグが終わるときはOnMouseUpForMoveSelectionメソッドが呼ばれます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public partial class UserControlImage : UserControl { private void PictureBox1_MouseUp(object sender, MouseEventArgs e) { if(Bitmap == null) return; if(isMouseDown) { if(EditMode == EditMode.Selection) OnMouseUpForSelectRectangle(); if(EditMode == EditMode.DrawRectangle || EditMode == EditMode.FillRectangle || EditMode == EditMode.DrawEllipse || EditMode == EditMode.FillEllipse) OnMouseUpForDrawRectangle(); if(EditMode == EditMode.DrawLine) OnMouseUpForDrawLine(new Point(e.X + ScrollBarPosX, e.Y + ScrollBarPosY)); if(EditMode == EditMode.FreeLine) OnMouseUpForDrawFreeLine(new Point(e.X + ScrollBarPosX, e.Y + ScrollBarPosY)); return; } if(isMouseDownForMove) OnMouseUpForMoveSelection(new Point(e.X + ScrollBarPosX, e.Y + ScrollBarPosY)); } } |
OnMouseUpForDrawRectangleメソッドは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 |
public partial class UserControlImage : UserControl { void OnMouseUpForDrawRectangle() { isMouseDown = false; Rectangle rect = GetRectangle(startPoint, endPoint); if(rect.IsEmpty || rect.Width == 0 || rect.Height == 0) return; Bitmap rectBitmap = new Bitmap(rect.Width + 1, rect.Height + 1); Graphics g = Graphics.FromImage(rectBitmap); if(EditMode == EditMode.DrawRectangle) g.DrawRectangle(new Pen(DrawColor), new Rectangle(0,0,rect.Width, rect.Height)); if(EditMode == EditMode.FillRectangle) g.FillRectangle(new SolidBrush(DrawColor), new Rectangle(0, 0, rect.Width, rect.Height)); if(EditMode == EditMode.DrawEllipse) g.DrawEllipse(new Pen(DrawColor), new Rectangle(0, 0, rect.Width, rect.Height)); if(EditMode == EditMode.FillEllipse) g.FillEllipse(new SolidBrush(DrawColor), new Rectangle(0, 0, rect.Width, rect.Height)); g.Dispose(); BitmapRectangle = new BitmapRectangle(new Bitmap(rectBitmap), rect); } } |
OnMouseUpForMoveSelectionメソッドはisMouseDownForMoveフラグをクリアしてBitmapRectangle.UpdatePositionメソッドを呼びます。
移動が終わったとしてもそこからさらにもう一度移動される可能性があります。そのときは移動先の位置が新たな移動元になります。そこで座標やサイズを保存しなおしているのです。
1 2 3 4 5 6 7 8 |
public partial class UserControlImage : UserControl { void OnMouseUpForMoveSelection(Point bitmapPoint) { isMouseDownForMove = false; BitmapRectangle.UpdatePosition(); } } |
選択部分の移動が確定するときとは?
本当にBitmapプロパティが更新されるのは範囲選択が解除されたときです。UniteBitmapRectangleメソッドはBitmapプロパティとBitmapRectangleプロパティを完全に統合させるものです。これによって移動した選択範囲の位置が確定します。
使われなくなったBitmapRectangleとblankBitmapはDisposeされ、そのなかに格納されていたビットマップデータもDisposeされます。
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 |
public partial class UserControlImage : UserControl { void OnMouseDownOutOfSelection(Point bitmapPoint) { startPoint = new Point(-1, -1); endPoint = new Point(-1, -1); UniteBitmapRectangle(bitmapPoint); ShowBitmap(Bitmap); } void UniteBitmapRectangle(Point mouseDownPoint) { Bitmap bitmap1 = new Bitmap(Bitmap); Graphics g = Graphics.FromImage(bitmap1); if(blankBitmap != null) { g.DrawImage(blankBitmap.Bitmap, blankBitmap.Location); blankBitmap.Dispose(); blankBitmap = null; } if(BitmapRectangle != null) { g.DrawImage(BitmapRectangle.Bitmap, BitmapRectangle.Location); g.Dispose(); BitmapRectangle.Dispose(); BitmapRectangle = null; } Bitmap = bitmap1; startPoint = new Point(-1, -1); endPoint = new Point(-1, -1); } } |