1

I have to fill a section inside PicureBox with solid color. I've managed to do it with this code:

private void fillForm(object sender, MouseEventArgs e)
        {
            MouseEventArgs me = (MouseEventArgs)e;
            Point coordinates = me.Location; // get mouse click coordinates
            Graphics graphics = Graphics.FromImage(imageSection.Image);
            fillDownRight(coordinates);
            fillUpLeft(new Point(coordinates.X - 1, coordinates.Y));
            fillUpRight(new Point(coordinates.X, coordinates.Y - 1));
            fillDownLeft(new Point(coordinates.X - 1, coordinates.Y + 1));
            graphics.Dispose();
            imageSection.Refresh();
        }

        private void fillDownRight(Point coordinates)
        {
            Bitmap bitmap = ((Bitmap)imageSection.Image);

            if (coordinates.Y < bitmap.Height && coordinates.X + 1 < bitmap.Width)
            {
                int pixelColor = bitmap.GetPixel(coordinates.X, coordinates.Y).ToArgb();
                if (pixelColor != Color.Black.ToArgb() && pixelColor != pen.Color.ToArgb())
                {
                    bitmap.SetPixel(coordinates.X, coordinates.Y, pen.Color);
                    fillDownRight(new Point(coordinates.X, coordinates.Y + 1));
                    fillDownRight(new Point(coordinates.X + 1, coordinates.Y));
                }
            }
        }
        private void fillUpLeft(Point coordinates)
        {
            Bitmap bitmap = ((Bitmap)imageSection.Image);

            if (coordinates.Y >= Constants.coordinatesZero && coordinates.X >= Constants.coordinatesZero)
            {
                int pixelColor = bitmap.GetPixel(coordinates.X, coordinates.Y).ToArgb();
                if (pixelColor != Color.Black.ToArgb() && pixelColor != pen.Color.ToArgb())
                {
                    bitmap.SetPixel(coordinates.X, coordinates.Y, pen.Color);
                    fillUpLeft(new Point(coordinates.X, coordinates.Y - 1));
                    fillUpLeft(new Point(coordinates.X - 1, coordinates.Y));
                }
            }
        }

        private void fillUpRight(Point coordinates)
        {
            Bitmap bitmap = ((Bitmap)imageSection.Image);


            if (coordinates.Y >= Constants.coordinatesZero && coordinates.X < bitmap.Width)
            {
                int pixelColor = bitmap.GetPixel(coordinates.X, coordinates.Y).ToArgb();
                if (pixelColor != Color.Black.ToArgb() && pixelColor != pen.Color.ToArgb())
                {
                    bitmap.SetPixel(coordinates.X, coordinates.Y, pen.Color);
                    fillUpRight(new Point(coordinates.X, coordinates.Y - 1));
                    fillUpRight(new Point(coordinates.X + 1, coordinates.Y));
                }
            }
        }

        private void fillDownLeft(Point coordinates)
        {
            Bitmap bitmap = ((Bitmap)imageSection.Image);
            if (coordinates.Y < bitmap.Height && coordinates.X > Constants.coordinatesZero)
            {
                int pixelColor = bitmap.GetPixel(coordinates.X, coordinates.Y).ToArgb();
                if (pixelColor != Color.Black.ToArgb() && pixelColor != pen.Color.ToArgb())
                {
                    bitmap.SetPixel(coordinates.X, coordinates.Y, pen.Color);
                    fillDownLeft(new Point(coordinates.X - 1, coordinates.Y));
                    fillDownLeft(new Point(coordinates.X, coordinates.Y + 1));
                }
            }
        }

So basically my code does the following:

  1. get the coordinates where I clicked
  2. then if fills recursively all "blank" pixels(that doesn't have a color) until it finds a colored/black pixel

My PictureBox kind of looks like this:

enter image description here

And if I click on the selected area: enter image description here

The PictureBox should look like this: enter image description here

I want to ask if there is a faster/better way to fill the section.

t3nsa
  • 680
  • 6
  • 21
  • 1
    https://stackoverflow.com/a/45299760/14171304 – dr.null Jun 05 '22 at 08:15
  • 1
    Note that this will not be blindingly fast as it uses Get/SetPixel. You would need to use a FastBitmap or go for LockBits with adapted getter/setter routines to be really fast. But I found the floddfill to be fast enough for my purposes at the time.. – TaW Jun 05 '22 at 14:46

1 Answers1

1

I wrote code for flood-fill using LinkedList<>. Using Stack<> seems slower as it has to resize its inner array when it gets filled. (Source code of Stack<>)

static class FloodFill
{
    public static void Fill(this Bitmap bitmap, Color sourceColor,
             Color newColor, int x, int y)
    {
        var sourceArgb = sourceColor.ToArgb();
        var newArgb = newColor.ToArgb();
        if (sourceArgb == newArgb)
        {
            return;
        }
        LinkedList<Point> list = new LinkedList<Point>();
        list.AddLast(new Point(x, y));
        do
        {
            x = list.Last.Value.X;
            y = list.Last.Value.Y;
            list.RemoveLast();
            if (x < 0 || x >= bitmap.Width || y < 0 || y >= bitmap.Height)
            {
                list.RemoveLast();
                continue;
            }
            if (bitmap.GetPixel(x, y).ToArgb() == sourceArgb)
            {
                bitmap.SetPixel(x, y, newColor);
                list.AddLast(new Point(x + 1, y));
                list.AddLast(new Point(x - 1, y));
                list.AddLast(new Point(x, y + 1));
                list.AddLast(new Point(x, y - 1));
            }
        }
        while (list.Last != null);
    }
}

Client code is

private void PictureBox1_MouseClick(object sender, MouseEventArgs e)
{
    Bitmap bitmap = pictureBox1.Image as Bitmap;
    bitmap.Fill(bitmap.GetPixel(e.X, e.Y), Color.Blue, e.X, e.Y);
    pictureBox1.Invalidate();
}
DotNet Developer
  • 2,973
  • 1
  • 15
  • 24