В этой статье мы представим реализацию алгоритма заливки (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();
        }

    }
}