# FloodFill Algorithm in Detail

What is the FloodFill algorithm? A real-life example is color filling. In the default Windows application

*Paint*, using the bucket icon, we can fill the selected area with a color.floodfill

There are other applications of the FloodFill algorithm. Another example would be Minesweeper. Sometimes when you click on a tile, an area will expand out. The process of expansion is implemented through the FloodFill algorithm.

Minesweeper

Similarly, those puzzle-matching games such as Candy Crush also use the FloodFill algorithm to remove blocks of the same color.

xiaoxiaole

Now you should have some idea about the FloodFill algorithm. Let's abstract out the problems and find out what is common.

All above examples can be abstract as a 2D array. In fact, a picture is an array of pixels. We take an element as the starting point and expand till the end.

An array can be further abstracted as a graph. Hence, the problem becomes about traversing a graph, similar to traversing an N-ary tree. A few lines of code are enough to resolve the problem. Here is the framework:

// (x, y) represents the coordinate

void fill(int x, int y) {

fill(x - 1, y); // up

fill(x + 1, y); // down

fill(x, y - 1); // left

fill(x, y + 1); // right

}

Using this framework, we can resolve all problems about traversing a 2D array. The concept is also called Depth First Search (DFS), or quaternary (4-ary) tree traversal. The root node is coordinate (x, y). Its four child nodes are at root's four directions.

title

In another article, we discussed a generic design of tree related algorithms. We can apply the concept here:

int[][] floodFill(int[][] image,

int sr, int sc, int newColor) {

int origColor = image[sr][sc];

fill(image, sr, sc, origColor, newColor);

return image;

}

void fill(int[][] image, int x, int y,

int origColor, int newColor) {

// OUT: out of index

if (!inArea(image, x, y)) return;

// CLASH: meet other colors, beyond the area of origColor

if (image[x][y] != origColor) return;

image[x][y] = newColor;

fill(image, x, y + 1, origColor, newColor);

fill(image, x, y - 1, origColor, newColor);

fill(image, x - 1, y, origColor, newColor);

fill(image, x + 1, y, origColor, newColor);

}

boolean inArea(int[][] image, int x, int y) {

return x >= 0 && x < image.length

&& y >= 0 && y < image[0].length;

}

If you can understand this block of code, you are almost there! It means that you have honed the mindset of framework. This block of code can cover 99% of cases. There is only one tiny problem to be resolved: an infinite loop will happen if

`origColor`

is the same as `newColor`

.Why is there infinite loop? Each coordinate needs to go through its 4 neighbors. Consequently, each coordinate will also be traversed 4 times by its 4 neighbors.

**When we visit an visited coordinate, we must guarantee to identify the situation and exit. If not, we'll go into infinite loop.**Why can the code exit properly when

`newColr`

and `origColor`

are different? Let's draw an diagram of the algorithm execution:ppt1

As we can see from the diagram,

`fill(1, 1)`

is visited twice. Let's use `fill(1, 1)*`

to represent this duplicated visit. When `fill(1, 1)*`

is executed, `(1, 1)`

has already been replaced with `newColor`

. So `fill(1, 1)*`

will return the control directly at the *CLASH*, i.e. exit as expected.// CLASH: meet other colors, beyond the area of origColor

if (image[x][y] != origColor) return;

ppt2

However, if

`origColor`

is the same as `newCOlor`

, `fill(1, 1)*`

will not exit at the *CLASH*. Instead, an infinite loop will start as shown below.ppt3

How to avoid the case of infinite loop? The most intuitive answer is to use a boolean 2D array of the same size as image, to record whether a coordinate has been traversed or not. If visited, return immediately.

// OUT: out of index

if (!inArea(image, x, y)) return;

// CLASH: meet other colors, beyond the area of origColor

if (image[x][y] != origColor) return;

// VISITED: don't visit a coordinate twice

if (visited[x][y]) return;

visited[x][y] = true;

image[x][y] = newColor;

This is a common technique to handle graph related problems. For this particular problem, there is actually a better way: backtracking algorithm.

Refer to the article Backtracking Algorithm in Depth for details. We directly apply the backtracking algorithm framework here:

void fill(int[][] image, int x, int y,

int origColor, int newColor) {

// OUT: out of index

if (!inArea(image, x, y)) return;

// CLASH: meet other colors, beyond the area of origColor

if (image[x][y] != origColor) return;

// VISITED: visited origColor

if (image[x][y] == -1) return;

// choose: mark a flag as visited

image[x][y] = -1;

fill(image, x, y + 1, origColor, newColor);

fill(image, x, y - 1, origColor, newColor);

fill(image, x - 1, y, origColor, newColor);

fill(image, x + 1, y, origColor, newColor);

// unchoose: replace the mark with newColor

image[x][y] = newColor;

}

This is a typical way, using a special value -1 to replace the visited 2D array, to achieve the same purpose. Because the range of color is

`[0, 65535]`

, -1 is special enough to differentiate with actual colors.Most picture editing softwares have the function "Magic Wand Tool". When you click a point, the application will help you choose a region of similar colors automatically. Refer to the picture below, if we want to select the eagle, we can use the Magic Wand Tool to select the blue sky, and perform inverse selection. Let's analyze the mechanism of the Magic Wand Tool.

CutOut

Obviously, the algorithm must be based on the FloodFill algorithm. However, there are two differences: 1. Though the background color is blue, we can't guarantee all the blue pixels are exactly the same. There could be minor differences that can be told by our eyes. But we still want to ignore these minor differences. 2. FloodFill is to fill regions. Magic Wand Tool is more about filling the edges.

It's easy to resolve the first problem by setting a

`threshold`

. All colors within the threshold from the `origColor`

can be recognized as `origColor`

.if (Math.abs(image[x][y] - origColor) > threshold)

return;

As for the second problem, let's first define the problem clearly:

*"do not color all*`origColor`

*coordinates in the region; only care about the edges."*. Next, let's analyze how to only color edges. i.e. How to find out the coordinates at the edges? What special properties do coordinates at the edges hold?ppt4

From the diagram above, we can see that for all coordinates at the edges, there is at least one direction that is not

`origColor`

. For all inner coordinates, all 4 directions are `origColor`

. This is the key to the solution. Using the same framework, using `visited`

array to represent traversed coordinates:int fill(int[][] image, int x, int y,

int origColor, int newColor) {

// OUT: out of index

if (!inArea(image, x, y)) return 0;

// VISITED: visited origColor

if (visited[x][y]) return 1;

// CLASH: meet other colors, beyond the area of origColor

if (image[x][y] != origColor) return 0;

visited[x][y] = true;

int surround =

fill(image, x - 1, y, origColor, newColor)

+ fill(image, x + 1, y, origColor, newColor)

+ fill(image, x, y - 1, origColor, newColor)

+ fill(image, x, y + 1, origColor, newColor);

if (surround < 4)

image[x][y] = newColor;

return 1;

}

In this way, all inner coordinates will have

`surround`

equal to 4 after traversing the four directions; all edge coordinates will be either OUT or CLASH, resulting `surround`

less than 4. If you are still not clear, let's only look at the framework's logic flow:int fill(int[][] image, int x, int y,

int origColor, int newColor) {

// OUT: out of index

if (!inArea(image, x, y)) return 0;

// VISITED: visited origColor

if (visited[x][y]) return 1;

// CLASH: meet other colors, beyond the area of origColor

if (image[x][y] != origColor) return 0;

// UNKNOWN: unvisited area that is origColor

if (image[x][y] == origColor) {

// ...

return 1;

}

}

These 4

`if`

s cover all possible scenarios of (x, y). The value of `surround`

is the sum of the return values of the 4 recursive functions. And each recursive function will fall into one of the 4 scenarios. You should be much clearer now after looking at this framework.This implementation colors all edge coordinates only for the

`origColor`

region, which is what the Magic Wand TOol does.Pay attention to 2 details in this algorithm: 1. We must use

`visited`

to record traversed coordinates instead of backtracking algorithm. 2. The order of the `if`

clauses can't be modified. (Why?)Similarly, for Minesweeper, when we use the FloodFill algorithm to expand empty areas, we also need to show the number of mines nearby. How to implement it? Following the same idea, return

`true`

when we meet mine. Thus, `surround`

will store the number of mines nearby. Of course, in Minesweeper, there are 8 directions instead of 4, including diagonals.We've discussed the design and framework of the FloodFill algorithm.

**All searching problems in a 2D array can be fit into this framework.**Last modified 3yr ago