В этой статье мы представим реализацию алгоритма заливки (flood fill) на языке C#. Будем использовать метод правой руки.
Сначала создадим форму и зададим ей нужные параметры.
Form form1 = new Form();
form1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly;
form1.FormBorderStyle = FormBorderStyle.FixedSingle;
form1.BackColor = Color.White;
form1.Text = "FloodFill";
form1.Bounds = new Rectangle(100, 100, 1000, 500);На форме создадим растровый слой bitmap. Это позволит нам работать с отдельными пикселями.
bitmap = new Bitmap(form1.ClientSize.Width, form1.ClientSize.Height);Нарисуем несколько базовых геометрических фигур на нашем bitmap. В будущем мы сможем протестировать на них наш алгоритм.
static void circle_paint()
{
using (Graphics gr = Graphics.FromImage(bitmap))
{
// Зададим параметры разных кистей
using (SolidBrush back = new SolidBrush(Color.White))
using (Pen pnBlue = new Pen(Color.Blue, 5))
using (Pen pnBlack = new Pen(Color.Black, 3))
using (Pen pnGreen = new Pen(Color.Green, 3))
{
// Установим фон
gr.FillRectangle(back, 0, 0, 1000, 500);
// Два пересекающихся круга
gr.DrawEllipse(pnBlue, 200, 200, 100, 100);
gr.DrawEllipse(pnBlue, 250, 200, 100, 100);
// Пара пересекающихся прямоугольников
gr.DrawRectangle(pnBlack, 400, 150, 120, 80);
gr.DrawRectangle(pnBlack, 450, 180, 120, 80);
// Треугольник
Point[] triangle = new Point[]
{
new Point(150, 100),
new Point(100, 160),
new Point(200, 160),
};
gr.DrawPolygon(pnGreen, triangle);
// Пятиугольник
Point[] pentagon = new Point[]
{
new Point(600, 260),
new Point(570, 300),
new Point(590, 345),
new Point(635, 345),
new Point(655, 300),
};
gr.DrawPolygon(pnBlack, pentagon);
// Два концентрических круга (внутренний отделяет область)
gr.DrawEllipse(pnBlue, 720, 90, 140, 140); // внешний
gr.DrawEllipse(pnBlue, 750, 120, 80, 80); // внутренний
// Ломаная линия с замыканием
Point[] poly = new Point[]
{
new Point(320, 280),
new Point(360, 260),
new Point(400, 300),
new Point(380, 340),
new Point(330, 330),
};
gr.DrawPolygon(pnGreen, poly);
}
}Вызвав в основной программе метод circle_paint, мы можем записать данное отображение в bitmap. Но это всё ещё не даст нам отрисованное изображение. Чтобы увидеть результат, нужно подключить к форме метод Paint. Также добавим в форму метод MouseUp.
form1.Paint += new PaintEventHandler(form1_Paint);
form1.MouseUp += new MouseEventHandler(form1_MouseUp);static void form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics gr = e.Graphics;
if (bitmap != null) gr.DrawImage(bitmap, 0, 0, bitmap.Width, bitmap.Height);
}Стартовое отображение:

Метод MouseUp позволит нам при клике в любую точку bitmap получить точку затравки.
Точка затравки - начальная точка от которой и будет происходить заливка.
Перейдём к методу.
static void form1_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
{
// Проверим входит ли наша точка в размеры bitmap
if (e.X < 0 || e.X >= bitmap.Width || e.Y < 0 || e.Y >= bitmap.Height)
return;
// Определим цвет в точке затравки
Color targetColor = bitmap.GetPixel(e.X, e.Y);
// Если цвет в точке затравки совпадает с цеветом заливки, то return
if (targetColor.ToArgb() == fill.ToArgb())
return;
// Зададим стэк для метода правой руки и поместим туда точку затравки
Stack<Point> stack = new Stack<Point>();
stack.Push(new Point(e.X, e.Y));
//Выполняем все шаги, пока стэк не пуст
while (stack.Count > 0)
{
// Получаем текущую точку current
Point current = stack.Pop();
int x = current.X;
int y = current.Y;
// Проверка границ
if (x < 0 || x >= bitmap.Width || y < 0 || y >= bitmap.Height)
continue;
// Получим цвет в текущей точке
Color pixelColor = bitmap.GetPixel(x, y);
// Если цвет не совпадает с целевым или уже залит — пропускаем
if (pixelColor.ToArgb() != targetColor.ToArgb() ||
pixelColor.ToArgb() == fill.ToArgb())
continue;
// Заливаем пиксель в текущей точке
bitmap.SetPixel(x, y, fill);
// Добавляем соседние пиксели в стек методом правой руки
stack.Push(new Point(x + 1, y)); // вправо
stack.Push(new Point(x, y + 1)); // вниз
stack.Push(new Point(x - 1, y)); // влево
stack.Push(new Point(x, y - 1)); // вверх
}
// Обновляем отображение
((Control)sender).Invalidate();
}Добавим в Main кнопки для выбора цвета заливки.
Результат работы программы.

Полный текст программы:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace FloodFill
{
static internal class Program
{
static Bitmap bitmap;
static Color fill = Color.Red;
[STAThread]
static void Main()
{
Form form1 = new Form();
form1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly;
form1.FormBorderStyle = FormBorderStyle.FixedSingle;
form1.BackColor = Color.White;
form1.Text = "FloodFill";
form1.Bounds = new Rectangle(100, 100, 1000, 500);
// Панель с кнопками выбора цвета заливки
Panel bottomPanel = new Panel();
bottomPanel.Dock = DockStyle.Bottom;
bottomPanel.Height = 44;
bottomPanel.BackColor = Color.Gainsboro;
form1.Controls.Add(bottomPanel);
Button btnRed = new Button();
btnRed.Text = "Red";
btnRed.BackColor = Color.Red;
btnRed.ForeColor = Color.White;
btnRed.Width = 70;
btnRed.Left = 10;
btnRed.Top = 8;
btnRed.FlatStyle = FlatStyle.Popup;
btnRed.Click += (s, e) => { fill = Color.Red; };
bottomPanel.Controls.Add(btnRed);
Button btnGreen = new Button();
btnGreen.Text = "Green";
btnGreen.BackColor = Color.Green;
btnGreen.ForeColor = Color.White;
btnGreen.Width = 70;
btnGreen.Left = btnRed.Right + 10;
btnGreen.Top = 8;
btnGreen.FlatStyle = FlatStyle.Popup;
btnGreen.Click += (s, e) => { fill = Color.Green; };
bottomPanel.Controls.Add(btnGreen);
Button btnBlue = new Button();
btnBlue.Text = "Blue";
btnBlue.BackColor = Color.Blue;
btnBlue.ForeColor = Color.White;
btnBlue.Width = 70;
btnBlue.Left = btnGreen.Right + 10;
btnBlue.Top = 8;
btnBlue.FlatStyle = FlatStyle.Popup;
btnBlue.Click += (s, e) => { fill = Color.Blue; };
bottomPanel.Controls.Add(btnBlue);
Button btnBlack = new Button();
btnBlack.Text = "Black";
btnBlack.BackColor = Color.Black;
btnBlack.ForeColor = Color.White;
btnBlack.Width = 70;
btnBlack.Left = btnBlue.Right + 10;
btnBlack.Top = 8;
btnBlack.FlatStyle = FlatStyle.Popup;
btnBlack.Click += (s, e) => { fill = Color.Black; };
bottomPanel.Controls.Add(btnBlack);
Button btnYellow = new Button();
btnYellow.Text = "Yellow";
btnYellow.BackColor = Color.Yellow;
btnYellow.ForeColor = Color.Black;
btnYellow.Width = 70;
btnYellow.Left = btnBlack.Right + 10;
btnYellow.Top = 8;
btnYellow.FlatStyle = FlatStyle.Popup;
btnYellow.Click += (s, e) => { fill = Color.Yellow; };
bottomPanel.Controls.Add(btnYellow);
// Создаем bitmap по реальному клиентскому размеру (без панели)
bitmap = new Bitmap(form1.ClientSize.Width, form1.ClientSize.Height);
circle_paint();
form1.Paint += new PaintEventHandler(form1_Paint);
form1.MouseUp += new MouseEventHandler(form1_MouseUp);
Application.EnableVisualStyles();
//Application.SetCompatibleTextRenderingDefault(false);
Application.Run(form1);
}
static void circle_paint()
{
using (Graphics gr = Graphics.FromImage(bitmap))
{
using (SolidBrush back = new SolidBrush(Color.White))
using (Pen pnBlue = new Pen(Color.Blue, 5))
using (Pen pnBlack = new Pen(Color.Black, 3))
using (Pen pnGreen = new Pen(Color.Green, 3))
{
gr.FillRectangle(back, 0, 0, 1000, 500);
// Два пересекающихся круга
gr.DrawEllipse(pnBlue, 200, 200, 100, 100);
gr.DrawEllipse(pnBlue, 250, 200, 100, 100);
// Пара пересекающихся прямоугольников
gr.DrawRectangle(pnBlack, 400, 150, 120, 80);
gr.DrawRectangle(pnBlack, 450, 180, 120, 80);
// Треугольник
Point[] triangle = new Point[]
{
new Point(150, 100),
new Point(100, 160),
new Point(200, 160),
};
gr.DrawPolygon(pnGreen, triangle);
// Пятиугольник
Point[] pentagon = new Point[]
{
new Point(600, 260),
new Point(570, 300),
new Point(590, 345),
new Point(635, 345),
new Point(655, 300),
};
gr.DrawPolygon(pnBlack, pentagon);
// Два концентрических круга
gr.DrawEllipse(pnBlue, 720, 90, 140, 140); // внешний
gr.DrawEllipse(pnBlue, 750, 120, 80, 80); // внутренний
// Ломаная линия с замыканием (многоугольник со сложной формой)
Point[] poly = new Point[]
{
new Point(320, 280),
new Point(360, 260),
new Point(400, 300),
new Point(380, 340),
new Point(330, 330),
};
gr.DrawPolygon(pnGreen, poly);
}
}
}
static void form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics gr = e.Graphics;
if (bitmap != null) gr.DrawImage(bitmap, 0, 0, bitmap.Width, bitmap.Height);
}
static void form1_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
{
// Проверим входит ли наша точка в размеры bitmap
if (e.X < 0 || e.X >= bitmap.Width || e.Y < 0 || e.Y >= bitmap.Height)
return;
// Определим цвет в точке затравки
Color targetColor = bitmap.GetPixel(e.X, e.Y);
// Если цвет в точке затравки совпадает с цеветом заливки, то return
if (targetColor.ToArgb() == fill.ToArgb())
return;
// Зададим стэк для метода правой руки и поместим туда точку затравки
Stack<Point> stack = new Stack<Point>();
stack.Push(new Point(e.X, e.Y));
//Выполняем все шаги, пока стэк не пуст
while (stack.Count > 0)
{
// Получаем текущую точку current
Point current = stack.Pop();
int x = current.X;
int y = current.Y;
// Проверка границ
if (x < 0 || x >= bitmap.Width || y < 0 || y >= bitmap.Height)
continue;
// Получим цвет в текущей точке
Color pixelColor = bitmap.GetPixel(x, y);
// Если цвет не совпадает с целевым или уже залит — пропускаем
if (pixelColor.ToArgb() != targetColor.ToArgb() ||
pixelColor.ToArgb() == fill.ToArgb())
continue;
// Заливаем пиксель в текущей точке
bitmap.SetPixel(x, y, fill);
// Добавляем соседние пиксели в стек методом правой руки
stack.Push(new Point(x + 1, y)); // вправо
stack.Push(new Point(x, y + 1)); // вниз
stack.Push(new Point(x - 1, y)); // влево
stack.Push(new Point(x, y - 1)); // вверх
}
// Обновляем отображение
((Control)sender).Invalidate();
}
}
}