選択した部分と同じ色を塗りつぶす処理を考えます。ペイントのこの部分と同じ処理です。
FillColorクラス
塗りつぶしの処理をするFillColorクラスを作成します。FormXクラスのなかに長々と書くとそれだけでクラスが大きくなってあとでみたときにわかりにくくなるからです。
コンストラクタとプロパティを示します。引数としてもとの画像とImageEditPictureBoxに表示される画像の両方を同時に変更できるようにコンストラクタの引数として渡しておきます。
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 class FillColor { public FillColor(PictureBoxUserControl control1, ImageEditPictureBox control2, int cellSize) { PictureBoxUserControl = control1; ImageEditPictureBox = control2; CellSize = cellSize; } PictureBoxUserControl PictureBoxUserControl { get; } ImageEditPictureBox ImageEditPictureBox { get; } int CellSize { get; } } |
クリックされた部分とつながったセルを取得する
クリックされた部分とつながった同じ色のピクセル座標を取得したい場合、再帰処理をつかったほうがスマートなのかもしれませんが、ここでは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 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 |
public class FillColor { public void FillConnectedColor(int clientX, int clientY, Color newColor) { int x = clientX / CellSize; int y = clientY / CellSize; // Bitmapのコピーをつくる Bitmap bitmap = new Bitmap(this.PictureBoxUserControl.Bitmap); Color oldColor = bitmap.GetPixel(x, y); // クリックされた部分の座標が変更後の色と同じ場合はなにもしない if (newColor.ToArgb() == oldColor.ToArgb()) { bitmap.Dispose(); return; } // X座標とY座標の最大値を取得 int maxX = bitmap.Width - 1; int maxY = bitmap.Height - 1; // Bitmapのコピーの色を変更する List<Cell> cells = new List<Cell>(); cells.Add(new Cell(x, y, newColor)); bitmap.SetPixel(x, y, newColor); // 色を変更すべきセルを取得するためのリスト List<Cell> changeCells = new List<Cell>(); while (cells.Count > 0) { List<Cell> tempCells = new List<Cell>(); foreach (Cell cell in cells) { x = cell.X; y = cell.Y; // 上下左右でつながったピクセルが最初にクリックされた部分と同じ色か調べる // このときにBitmap外の座標に対してGetPixelを実行しないように注意する // 同じ色なら変更後の色に変更して、その座標を仮のリストに格納する if (x - 1 >= 0) { Color color = bitmap.GetPixel(x - 1, y); if (color.ToArgb() == oldColor.ToArgb()) { bitmap.SetPixel(x - 1, y, newColor); tempCells.Add(new Cell(x - 1, y, newColor)); } } if (x + 1 <= maxX) { Color color = bitmap.GetPixel(x + 1, y); if (color.ToArgb() == oldColor.ToArgb()) { bitmap.SetPixel(x + 1, y, newColor); tempCells.Add(new Cell(x + 1, y, newColor)); } } if (y - 1 >= 0) { Color color = bitmap.GetPixel(x, y - 1); if (color.ToArgb() == oldColor.ToArgb()) { bitmap.SetPixel(x, y - 1, newColor); tempCells.Add(new Cell(x, y - 1, newColor)); } } if (y + 1 <= maxY) { Color color = bitmap.GetPixel(x, y + 1); if (color.ToArgb() == oldColor.ToArgb()) { bitmap.SetPixel(x, y + 1, newColor); tempCells.Add(new Cell(x, y + 1, newColor)); } } } // 色を変更すべきセルを取得するためのリストに取得したセルを追加する changeCells.AddRange(cells); // 仮のリストをforeach文で使うリストに変更 // もしも仮のリストの中身が空ならループは終了 cells = tempCells; } bitmap.Dispose(); // 取得したセルの情報を反映させる ReflectColor(changeCells, newColor); } } |
取得したセルの情報を反映させる
最後に取得したセルの情報を反映させれば処理は完了です。
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 class FillColor { void ReflectColor(List<Cell> cells, Color newColor) { Bitmap bitmap1 = PictureBoxUserControl.Bitmap; Bitmap bitmap2 = (Bitmap)ImageEditPictureBox.Image; Graphics graphics = Graphics.FromImage(bitmap2); SolidBrush solidBrush; if (newColor != Color.Empty) solidBrush = new SolidBrush(newColor); else solidBrush = new SolidBrush(Color.FromName("Control")); foreach (Cell cell in cells) { graphics.FillRectangle(solidBrush, new Rectangle(cell.X * CellSize, cell.Y * CellSize, CellSize, CellSize)); bitmap1.SetPixel(cell.X, cell.Y, cell.Color); } graphics.Dispose(); ImageEditPictureBox.Invalidate(); PictureBoxUserControl.Bitmap = bitmap1; } } |
選択された色で囲まれている部分を塗りつぶす
ところで写真に対してこの処理をすると複数のセルの色が変更されることはほとんどありません。同じ色のように見えるだけで微妙に異なる色が並んでいるからです。
写真の一部を矩形や多角形で囲んで内部を別の色で塗りたくなることもあるかもしれません。Paintでこの処理をする場合、矩形の場合は範囲選択してその部分をクリアして塗りつぶしの処理をすればいいのですが、不規則な多角形の場合はこれができません。
そこで選択された色で囲まれている部分全体を選択された色で塗りつぶす方法を考えます。ただしこの処理は選択された色で囲まれている図形のなかに途中で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 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 |
public class FillColor { // newColorで囲まれた領域をnewColorで塗りつぶす // 完全に囲まれた領域がない場合は全面を塗りつぶす public void ForFillEnclosedArea(int clientX, int clientY, Color newColor) { int x = clientX / CellSize; int y = clientY / CellSize; Bitmap bitmap = new Bitmap(this.PictureBoxUserControl.Bitmap); Color oldColor1 = bitmap.GetPixel(x, y); // クリックされた部分の座標が変更後の色と同じ場合はなにもしない if (newColor.ToArgb() == oldColor.ToArgb()) { bitmap.Dispose(); return; } int maxX = bitmap.Width - 1; int maxY = bitmap.Height - 1; List<Cell> cells = new List<Cell>(); cells.Add(new Cell(x, y, newColor)); List<Cell> changeCells = new List<Cell>(); while (cells.Count > 0) { List<Cell> tempCells = new List<Cell>(); foreach (Cell cell in cells) { x = cell.X; y = cell.Y; // 上下左右でつながったピクセルが変更後の色と同じか調べる // このときにBitmap外の座標に対してGetPixelを実行しないように注意する // 異なる色なら変更後の色に変更して、その座標を仮のリストに格納する if (x - 1 >= 0) { Color color = bitmap.GetPixel(x - 1, y); if (color.ToArgb() != newColor.ToArgb()) { bitmap.SetPixel(x - 1, y, newColor); tempCells.Add(new Cell(x - 1, y, newColor)); } } if (x + 1 <= maxX) { Color color = bitmap.GetPixel(x + 1, y); if (color.ToArgb() != newColor.ToArgb()) { bitmap.SetPixel(x + 1, y, newColor); tempCells.Add(new Cell(x + 1, y, newColor)); } } if (y - 1 >= 0) { Color color = bitmap.GetPixel(x, y - 1); if (color.ToArgb() != newColor.ToArgb()) { bitmap.SetPixel(x, y - 1, newColor); tempCells.Add(new Cell(x, y - 1, newColor)); } } if (y + 1 <= maxY) { Color color = bitmap.GetPixel(x, y + 1); if (color.ToArgb() != newColor.ToArgb()) { bitmap.SetPixel(x, y + 1, newColor); tempCells.Add(new Cell(x, y + 1, newColor)); } } } changeCells.AddRange(cells); cells = tempCells; } bitmap.Dispose(); ReflectColor(changeCells, newColor); } } |
あとはForm2クラスから呼び出せるようにするだけです。
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 Form2 : Form { private void ImageEditPictureBox_MouseDown(object sender, MouseEventArgs e) { // その他の処理の場合は省略 if (this.RadioButtonFillConnectedColor.Checked) MouseDownForFillConnectedColor(e.X, e.Y); if (this.RadioButtonFillEnclosedArea.Checked) MouseDownForFillEnclosedArea(e.X, e.Y); } // 同じ色で繋がった領域を塗りつぶす void MouseDownForFillConnectedColor(int clientX, int clientY) { FillColor fillColor = new FillColor(PictureBoxUserControl, ImageEditPictureBox, CellSize); fillColor.FillConnectedColor(clientX, clientY, SelectedColor); } // 指定色で囲まれた領域を塗りつぶす void MouseDownForFillEnclosedArea(int clientX, int clientY) { FillColor fillColor = new FillColor(PictureBoxUserControl, ImageEditPictureBox, CellSize); fillColor.ForFillEnclosedArea(clientX, clientY, SelectedColor); } } |