Erik Klop
Erik Klop

Reputation: 135

Filling rectangles

Ive created a winform-program that uses a Graphics object and 2 for-loops to generate a square grid, depending on user input.

I also created a method that fills each square in the grid with a random color by using the same coordinates as the grid.

Now I want to paint each square independently by clicking on it, using the cursor position. How should i do this?

Upvotes: 1

Views: 401

Answers (2)

Pedery
Pedery

Reputation: 3636

Why don't you just keep track of the gridlines and thus know what square you clicked within? From this knowledge you could paint a square where one belongs.

Upvotes: 2

3Dave
3Dave

Reputation: 29061

A flood fill is easiest. It's slow compared to other methods and eats stack space, but it shouldn't be a problem on a computer that's less than 15 years old.

Update

As @Ron mentioned, a typical recursive floodfill blows the stack pretty easily. So, I modified the code to use a Stack<> instance (which I believe is allocated from the heap) and so-called "data recursion". It's still pretty slow for large (2000x2000+ pixel) areas, but should be just fine for small ones.

bool[] canDraw;
/// <summary>
/// make sure that the given point is within our image boundaries.
/// BufferSize(Point) contains the dimensions of the image buffer.
/// </summary>
/// <param name="p"></param>
/// <returns></returns>
bool InBounds(Point p)
{
    return p.X >= 0 && p.X < BufferSize.X && p.Y >= 0 && p.Y < BufferSize.Y;
}

/// <summary>
/// make sure that we haven't already drawn this pixel and that it has
/// valid coordinates
/// </summary>
/// <param name="p"></param>
/// <returns></returns>
bool CanDraw(Point p)
{
    return InBounds(p) && canDraw[p.Y * BufferSize.X + p.X];
}

/// <summary>
/// Heap "stack" to track which pixels we need to visit
/// </summary>
Stack<Point> fillStack = new Stack<Point>();

/// <summary>
/// initialize recursion.
/// </summary>
/// <param name="startPosition"></param>
/// <param name="fillColor"></param>
void Fill(Point startPosition, Color fillColor)
{
    canDraw = Enumerable.Repeat(true, BufferSize.X * BufferSize.Y).ToArray();
    var backgroundColor = GetPixel(startPosition);

    if (backgroundColor != fillColor)
    {
        fillStack.Push(startPosition);
        RecurseFloodFill(fillColor, backgroundColor);
    }

}

/// <summary>
/// data-recurse through the image.
/// </summary>
/// <param name="fillColor">Color we want to fill with</param>
/// <param name="backgroundColor">Initial background color to overwrite</param>
void RecurseFloodFill(Color fillColor, Color backgroundColor)
{
    while (fillStack.Count > 0 && !IsExiting)
    {
        /*if (fillStack.Count != depth)
            Debug.WriteLine("Depth: {0}", depth = fillStack.Count);
        */
        var position = fillStack.Pop();
        if(!CanDraw(position))
            continue;

        var color = GetPixel(position);
        if (color != backgroundColor)
            continue;

        SetPixel(position, fillColor);

        for(var i=position.X-1;i<=position.X+1;i++)
            for (var j = position.Y - 1; j <= position.Y + 1; j++)
            {
                var p = new Point(i, j);
                fillStack.Push(p);
            }

    }

}

Note that I haven't even tried to compile this.

Basically:

  1. On click: read the color (background color) where the user clicked.
  2. Recursion start:
  3. read the color (background color) where the user clicked.
  4. Make sure it's the same as the background color from step 1. Else return.
  5. Set the pixel to the fill color.
  6. Recurse to step 3 for the 8 surrounding pixels. Recursion stops when you read a background pixel that's different than the initially-read background color.

Upvotes: 1

Related Questions