Skip to content

Commit b689488

Browse files
authoredAug 2, 2020
Flood fill chapter (algorithm-archivists#738)
* adding rough draft of flood fill chapter * adding video to chapter
1 parent b537da3 commit b689488

17 files changed

+735
-0
lines changed
 

‎SUMMARY.md

+2
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,7 @@
3636
* [Split-Operator Method](contents/split-operator_method/split-operator_method.md)
3737
* [Data Compression](contents/data_compression/data_compression.md)
3838
* [Huffman Encoding](contents/huffman_encoding/huffman_encoding.md)
39+
* [Computer Graphics](contents/computer_graphics/computer_graphics.md)
40+
* [Flood Fill](contents/flood_fill/flood_fill.md)
3941
* [Quantum Information](contents/quantum_information/quantum_information.md)
4042
* [Computus](contents/computus/computus.md)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Computer Graphics
2+
3+
Of all areas of computer science research, none have had more of an immediate impact on multimedia than computer graphics.
4+
This sub-field is distinctly different than computational science in that it focuses on the *appearance* of realistic details, instead of computing those details precisely.
5+
Where a computational scientist might spend years writing software that runs on the fastest computers known to man to simulate climate, the computer graphics researcher might apply machine learning to create fluid simulations that look good enough to the untrained eye.
6+
In the end, the computational scientist will have a plot and the computer graphics researcher will have a beautifully rendered simulation.
7+
8+
Though I may have painted computer graphics to be a bit hand-wavey, that could not be further from the truth!
9+
Instead, I would argue that this field of research provides the closest approximation to realistic visualizations that desktop hardware can currently support.
10+
Many art and video game studios are interested in telling a complete story via computational media, and this simply would not be possible without the rigorous efforts of researchers from around the world.
11+
This is why Pixar hires researchers and will actively publish their findings after their movies are released.
12+
13+
Though the boundary between computer science research fields is a bit vague, for the purposes of the Algorithm Archive, we will broadly classify computer graphics as anything with direct applications to images or fields that can be represented as images.
14+
Convolutions, for example, would not be considered part of computer graphics because they are used widely in all areas of computer science research; however, Canny edge detection will be.
15+
We will also be covering a wide range of applications that are used for rendering high-resolution graphics and computational art.
16+
17+
As with all sections to the Algorithm Archive, this is a work in progress and subject to change, so feel free to let me know what you think!
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
using DataStructures
2+
using Test
3+
4+
# Function to check to make sure we are on the canvas
5+
function inbounds(canvas_size, loc)
6+
7+
# Make sure we are not beneath or to the left of the canvas
8+
if minimum(Tuple(loc)) < 1
9+
return false
10+
11+
# Make sure we are not to the right of the canvas
12+
elseif loc[2] > canvas_size[2]
13+
return false
14+
15+
# Make sure we are not above the canvas
16+
elseif loc[1] > canvas_size[1]
17+
return false
18+
else
19+
return true
20+
end
21+
end
22+
23+
function color!(canvas, loc::CartesianIndex, old_val, new_val)
24+
# bounds check, do not color if no in bounds!
25+
if !inbounds(size(canvas), loc)
26+
return
27+
end
28+
29+
# Only change color if the element value is the old value
30+
if (canvas[loc] != old_val)
31+
return
32+
else
33+
canvas[loc] = new_val
34+
end
35+
end
36+
37+
function find_neighbors(canvas, loc::CartesianIndex, old_val, new_val)
38+
39+
# Finding north, south, east, west neighbors
40+
possible_neighbors = [loc + CartesianIndex(0, 1),
41+
loc + CartesianIndex(1, 0),
42+
loc + CartesianIndex(0, -1),
43+
loc + CartesianIndex(-1, 0)]
44+
45+
# Exclusing neighbors that should not be colored
46+
neighbors = []
47+
for possible_neighbor in possible_neighbors
48+
if inbounds(size(canvas), possible_neighbor) &&
49+
canvas[possible_neighbor] == old_val
50+
push!(neighbors, possible_neighbor)
51+
end
52+
end
53+
54+
return neighbors
55+
end
56+
57+
function stack_fill!(canvas, loc::CartesianIndex, old_val, new_val)
58+
if new_val == old_val
59+
return
60+
end
61+
62+
s = Stack{CartesianIndex}()
63+
push!(s, loc)
64+
65+
while length(s) > 0
66+
current_loc = pop!(s)
67+
if canvas[current_loc] == old_val
68+
color!(canvas, current_loc, old_val, new_val)
69+
possible_neighbors = find_neighbors(canvas, current_loc,
70+
old_val, new_val)
71+
for neighbor in possible_neighbors
72+
push!(s,neighbor)
73+
end
74+
end
75+
76+
end
77+
end
78+
79+
80+
function queue_fill!(canvas, loc::CartesianIndex, old_val, new_val)
81+
if new_val == old_val
82+
return
83+
end
84+
85+
q = Queue{CartesianIndex}()
86+
enqueue!(q, loc)
87+
88+
# Coloring the initial location
89+
color!(canvas, loc, old_val, new_val)
90+
91+
while length(q) > 0
92+
current_loc = dequeue!(q)
93+
94+
possible_neighbors = find_neighbors(canvas, current_loc,
95+
old_val, new_val)
96+
97+
# Coloring as we are enqueuing neighbors
98+
for neighbor in possible_neighbors
99+
color!(canvas, neighbor, old_val, new_val)
100+
enqueue!(q,neighbor)
101+
end
102+
103+
end
104+
end
105+
106+
function recursive_fill!(canvas, loc::CartesianIndex, old_val, new_val)
107+
108+
if (old_val == new_val)
109+
return
110+
end
111+
112+
color!(canvas, loc, old_val, new_val)
113+
114+
possible_neighbors = find_neighbors(canvas, loc, old_val, new_val)
115+
for possible_neighbor in possible_neighbors
116+
recursive_fill!(canvas, possible_neighbor, old_val, new_val)
117+
end
118+
end
119+
120+
function main()
121+
122+
# Creation of a 5x5 grid with a single row of 1.0 elements
123+
grid = zeros(5,5)
124+
grid[3,:] .= 1
125+
126+
# Create solution grid
127+
answer_grid = zeros(5,5)
128+
answer_grid[1:3, :] .= 1
129+
130+
# Start filling at 1,1
131+
start_loc = CartesianIndex(1,1)
132+
133+
@testset "Fill Methods" begin
134+
# Use recursive method and reinitialize grid
135+
recursive_fill!(grid, start_loc, 0.0, 1.0)
136+
@test grid == answer_grid
137+
138+
grid[1:2,:] .= 0
139+
140+
# Use queue method and reinitialize grid
141+
queue_fill!(grid, start_loc, 0.0, 1.0)
142+
@test grid == answer_grid
143+
144+
grid[1:2,:] .= 0
145+
146+
# Use stack method and reinitialize grid
147+
stack_fill!(grid, start_loc, 0.0, 1.0)
148+
@test grid == answer_grid
149+
end
150+
151+
end
152+
153+
main()

‎contents/flood_fill/flood_fill.md

+264
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
# Flood Fill
2+
3+
Flood fill is a method that is surprisingly useful in a large number of different situations and keeps finding me wherever I go.
4+
When I was completing my PhD, I had an idea to track superfluid vortices by using flood fill as a way to help mask out unnecessary features of the simulation.
5+
When I was making a terminal game, I thought of creating an animation that was just flood fill in disguise.
6+
When I decided to play minesweeper or Go with my girlfriend, flood fill was used in both!
7+
8+
Flood fill is probably most commonly known as the "Bucket Fill" application in most art programs {{ "gimp_bucket" | cite }}.
9+
It's usually indicated by an icon that looks like a bucket and is known to fill in any enclosed area, as shown below:
10+
11+
<p>
12+
<img class="center" src="res/example.png" style="width:100%" />
13+
</p>
14+
15+
Because flood fill is incredibly common, there are a large number of variations to the method, some of which are more optimal than others.
16+
For this chapter, we will cover the basics: how to fill a domain in a quick and dirty way.
17+
In subsequent chapters, we will continue our journey by creating more and more efficient flood fill methods, including scanline-based and fixed memory methods {{ "torbert2016" | cite }}.
18+
19+
I have decided to split the chapter up for a few important reasons:
20+
1. I did not want to flood the Algorithm Archive with flood fill methods all at the same time.
21+
I feel it's worth letting each chapter sit for a bit while we savor it's unique flavor.
22+
2. Many users are implementing versions of each algorithm in their own languages and it is difficult to review and submit code for chapters with a lot of code chunks.
23+
Several sub-chapters with less code is easier for everyone.
24+
3. I am kinda under a time-constraint right now and wanted to make sure we regularly get content into the Algorithm Archive.
25+
26+
So, without further a-do, let's hop right into it!
27+
28+
29+
## What does flood fill do?
30+
31+
Flood fill is essentially composed of 2 parts:
32+
1. Determining the extents of the domain to fill
33+
2. Walking through all elements within a domain and changing some property
34+
35+
For the purposes of this chapter, we will be using a set of floating-point values that range from 0 to 1 instead of a color-space like RGB.
36+
Though bucket fill is always used in art programs in some sort of color space, flood fill is more general and can be used in a space with any type of element.
37+
As such, it makes sense to use a simpler element type so we can better understand the method.
38+
39+
So how do we go about finding the extents of the domain to fill?
40+
41+
Here, a domain will be defined as any connected set of elements in an $$n$$-dimensional space whose values do not vary beyond a predefined threshold.
42+
As an example, if we take a circle embedded into a 2-dimensional grid, we have 3 separate domains:
43+
1. Inside the circle where all elements are 0.
44+
2. The circle, itself, where the elements are set to 0.75.
45+
3. Outside the circle where all elements are similarly 0.
46+
47+
<p>
48+
<img class="center" src="res/simple_circle.png" style="width:70%" />
49+
</p>
50+
51+
Though there are some more complicated ways to determine the extents of the domain, we will not focus on this aspect of the flood fill method for the remainder of this chapter and instead leave it for subsequent chapters.
52+
So now we will focus on the process of walking through each element in the domain and changing some property.
53+
54+
## Domain traversal
55+
56+
As before, the simplest example to work with is that of an image, where each element in our domain is a single pixel.
57+
Here, we can connect each pixel to all other pixels in its vicinity, like so:
58+
59+
<p>
60+
<img class="center" src="res/grid_1.png" style="width:70%" />
61+
</p>
62+
63+
In this image, a border is shown between each individual pixel and a grid is superimposed to show how each pixel is connected to its neighbors.
64+
This means that each element has 4 neighbors: north, south, east, and west.
65+
We could also include northeast, southeast, southwest, and northwest if we wanted to do an 8-way fill, but we will restrict the discussion to the 4-way fill for now, as the method is essentially the same and slightly easier to understand with fewer elements to worry about.
66+
67+
By connecting each pixel to its neighbors in this way, the flood fill operation becomes a process of graph traversal, not too dissimilar from the [tree traversal](../tree_traversal/tree_traversal.md) methods described before.
68+
This means that after selecting our initial location, we can then traverse through all elements in either a depth-first or breadth-first fashion.
69+
We will be covering the following this chapter:
70+
71+
1. Finding all neighbors
72+
2. Depth-first node traversal
73+
3. Breadth-first node traversal and small-scale optimizations
74+
75+
So let's start by discussing how we might go about finding the neighbors to fill.
76+
77+
### Finding all neighbors
78+
79+
The first step of this method is to query the location of all possible neighbors.
80+
At first glance, this seems rather straightforward.
81+
One simply needs to look up, down, left, and right of the current location and add those elements to the list of neighbors if they are:
82+
83+
1. On the canvas
84+
2. Have a value *close enough* to the old value we would like to replace
85+
86+
In code, this might look like this:
87+
88+
{% method %}
89+
{% sample lang="jl" %}
90+
[import:37-55, lang:"julia"](code/julia/flood_fill.jl)
91+
{% endmethod %}
92+
93+
94+
This code is set up to return a vector of elements to then use for subsequent sections.
95+
96+
### Depth-first node traversal
97+
98+
Now that we have the ability to find all neighboring elements, we can proceed to traverse through those nodes in the most straightforward way: recursion.
99+
100+
In code, it might look like this:
101+
102+
{% method %}
103+
{% sample lang="jl" %}
104+
[import:106-118, lang:"julia"](code/julia/flood_fill.jl)
105+
106+
All Julia code snippets for this chapter rely on an exterior `color!(...)` function, defined as
107+
108+
[import:23-35, lang:"julia"](code/julia/flood_fill.jl)
109+
{% endmethod %}
110+
111+
The above code continues recursing through available neighbors as long as neighbors exist, and this should work so long as we are adding the correct set of neighbors.
112+
113+
Additionally, it is possible to do the same type of traversal by managing a stack, like so:
114+
115+
{% method %}
116+
{% sample lang="jl" %}
117+
[import:57-77, lang:"julia"](code/julia/flood_fill.jl)
118+
{% endmethod %}
119+
120+
This is ultimately the same method of traversal as before; however, because we are managing our own data structure, there are a few distinct differences:
121+
1. The manually managed stack could be slightly slower and potentially more memory-intensive
122+
2. It is easy to reach the maximum recursion depth on certain hardware with the recursive method, so it is best to use the stack-based implementation in those cases.
123+
124+
If we were to use either of these methods to fill a circle embedded in a two dimensional domain, we would see the following
125+
126+
<div style="text-align:center">
127+
<video style="width:70%" controls loop>
128+
<source src="res/recurse_animation.mp4" type="video/mp4">
129+
Your browser does not support the video tag.
130+
</video>
131+
</div>
132+
133+
Here, we see that these methods will traverse through one direction first before filling from there.
134+
This is potentially the easiest method to write, but it is not the most intuitive fill pattern.
135+
I suspect that if someone was asked to fill the contents of the circle on their own, they would fill it more evenly from the center, like so:
136+
137+
<div style="text-align:center">
138+
<video style="width:70%" controls loop>
139+
<source src="res/queue_animation.mp4" type="video/mp4">
140+
Your browser does not support the video tag.
141+
</video>
142+
</div>
143+
144+
This is simply another traversal strategy known as breadth-first traversal and comes with its own set of caveats.
145+
We will discuss this further in the next subsection
146+
147+
### Breadth-first node traversal and small-scale optimizations
148+
149+
Breadth-first node traversal is as simple as switching the stack in the depth-first strategy with a queue.
150+
The code would look something like this:
151+
152+
{% method %}
153+
{% sample lang="jl" %}
154+
[import:80-104, lang:"julia"](code/julia/flood_fill.jl)
155+
{% endmethod %}
156+
157+
Now, there is a small trick in this code that must be considered to make sure it runs optimally.
158+
Namely, the nodes must be colored *when they are being enqueued*, not when visiting the node.
159+
At least for me, it was not immediately obvious why this would be the case, but let me try to explain.
160+
161+
Let's imagine that we decided to write code that colored all neighboring nodes only when visiting them.
162+
When querying all possible neighbors, we will add 4 elements to the queue for the north, south, east, and west neighbors of the initial node, as shown below:
163+
164+
<p>
165+
<img class="center" src="res/grid_2.png" style="width:70%" />
166+
</p>
167+
168+
Now let's imagine we travel east first.
169+
It then enqueues three more nodes: north, south, and east again.
170+
This is shown below:
171+
172+
<p>
173+
<img class="center" src="res/grid_3.png" style="width:70%" />
174+
</p>
175+
176+
It does not enqueue its west neighbor because this has already been colored.
177+
At this stage, we will have six nodes ready to be colored and 2 that are already colored.
178+
Now let's say we travel north next.
179+
This node will enqueue three more nodes: west, north, and east, as shown below:
180+
181+
<p>
182+
<img class="center" src="res/grid_4.png" style="width:70%" />
183+
</p>
184+
185+
The problem is that the east element has *already been enqueued for coloring by the previous node*!.
186+
This shared element is colored in red.
187+
As we progress through all four initial neighbors, we will find 4 nodes that are doubly enqueued: all directions diagonal to the initial location!
188+
This is again shown below:
189+
190+
<p>
191+
<img class="center" src="res/grid_5.png" style="width:70%" />
192+
</p>
193+
194+
As the number of nodes increases, so does the number of duplicate nodes.
195+
A quick fix is to color the nodes *when they are being enqueued* like in the example code above.
196+
When doing this, duplicates will not be enqueued with a breadth-first scheme because they will already be colored when other nodes are trying to find their neighbors.
197+
This created a node connection pattern like so:
198+
199+
<p>
200+
<img class="center" src="res/grid_6.png" style="width:70%" />
201+
</p>
202+
203+
As some final food for thought: why wasn't this a problem with the depth-first strategy?
204+
The simple answer is that it actually was an issue, but it was way less prevalent.
205+
With the depth-first strategy, a number of unnecessary nodes are still pushed to the stack, but because we consistently push one direction before spreading out to other directions, it is more likely that the nodes have filled neighbors when they are looking for what to fill around them.
206+
207+
Simply put: depth-first traversal is slightly more efficient in this case unless you can color as querying for neighbors, in which case breadth-first is more efficient.
208+
209+
## Conclusions
210+
211+
As stated before, the method discussed in this chapter is just the tip of the iceberg and many other flood fill methods exist that are likely to be more efficient for most purposes.
212+
These will all be covered in subsequent chapters which will come out somewhat regularly throughout the next few months, lest we flood that archive with flood fill methods.
213+
214+
## Video Explanation
215+
216+
Here is a video describing tree traversal:
217+
218+
<div style="text-align:center">
219+
<iframe width="560" height="315" src="https://www.youtube.com/embed/ldqAmkdthHY" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
220+
</div>
221+
222+
## Example Code
223+
224+
The example code for this chapter will be the simplest application of flood fill that still adequately tests the code to ensure it is stopping at boundaries appropriately.
225+
For this, we will create a two dimensional array of floats, all starting at 0.0, and then set a single vertical line of elements at the center to be 1.0.
226+
After, we will fill in the left-hand side of the array to be all ones by choosing any point within the left domain to fill.
227+
228+
{% method %}
229+
{% sample lang="jl" %}
230+
[import, lang:"julia"](code/julia/flood_fill.jl)
231+
{% endmethod %}
232+
233+
234+
### Bibliography
235+
236+
{% references %} {% endreferences %}
237+
238+
<script>
239+
MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
240+
</script>
241+
242+
## License
243+
244+
##### Code Examples
245+
246+
The code examples are licensed under the MIT license (found in [LICENSE.md](https://github.com/algorithm-archivists/algorithm-archive/blob/master/LICENSE.md)).
247+
248+
##### Text
249+
250+
The text of this chapter was written by [James Schloss](https://github.com/leio) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
251+
252+
[<p><img class="center" src="../cc/CC-BY-SA_icon.svg" /></p>](https://creativecommons.org/licenses/by-sa/4.0/)
253+
254+
##### Images/Graphics
255+
- The image "[Example Bucket Fill](res/example.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
256+
- The image "[Circle Domains](res/simple_circle.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
257+
- The image "[Grid 1](res/grid_1.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
258+
- The image "[Grid 2](res/grid_2.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
259+
- The image "[Grid 3](res/grid_3.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
260+
- The image "[Grid 4](res/grid_4.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
261+
- The image "[Grid 5](res/grid_5.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
262+
- The image "[Grid 6](res/grid_6.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
263+
- The video "[Stack Fill](res/recurse_animation.mp4)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
264+
- The video "[Queue Fill](res/queue_animation.mp4)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).

‎contents/flood_fill/flood_fill.md.bak

+258
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
# Flood Fill
2+
3+
Flood fill is a method that is surprisingly useful in a large number of different situations and keeps finding me wherever I go.
4+
When I was completing my PhD, I had an idea to track superfluid vortices by using flood fill as a way to help mask out unnecessary features of the simulation.
5+
When I was making a terminal game, I thought of creating an animation that was just flood fill in disguise.
6+
When I decided to play minesweeper or Go with my girlfriend, flood fill was used in both!
7+
8+
Flood fill is probably most commonly known as the "Bucket Fill" application in most art programs {{ "gimp_bucket" | cite }}.
9+
It's usually indicated by an icon that looks like a bucket and is known to fill in any enclosed area, as shown below:
10+
11+
<p>
12+
<img class="center" src="res/example.png" style="width:100%" />
13+
</p>
14+
15+
Because flood fill is incredibly common, there are a large number of variations to the method, some of which are more optimal than others.
16+
For this chapter, we will cover the basics: how to fill a domain in a quick and dirty way.
17+
In subsequent chapters, we will continue our journey by creating more and more efficient flood fill methods, including scanline-based and fixed memory methods {{ "torbert2016" | cite }}.
18+
19+
I have decided to split the chapter up for a few important reasons:
20+
1. I did not want to flood the Algorithm Archive with flood fill methods all at the same time.
21+
I feel it's worth letting each chapter sit for a bit while we savour it's unique flavour.
22+
2. Many users are implementing versions of each algorithm in their own languages and it is difficult to review and submit code for chapters with a lot of code chunks.
23+
Several sub-chapters with less code is easier for everyone.
24+
3. I am kinda under a time-constraint right now and wanted to make sure we regularly get content into the Algorithm Archive.
25+
26+
So, without further a-do, let's hop right into it!
27+
28+
29+
## What does flood fill do?
30+
31+
Flood fill is essentially composed of 2 parts:
32+
1. Determining the extents of the domain to fill
33+
2. Walking through all elements within a domain and changing some property
34+
35+
For the purposes of this chapter, we will be using a set of floating-point values that range from 0 to 1 instead of a color-space like RGB.
36+
Though bucket fill is always used in art programs in some sort of color space, flood fill is more general and can be used in a space with any type of element.
37+
As such, it makes sense to use a simpler element type so we can better understand the method.
38+
39+
So how do we go about finding the extents of the domain to fill?
40+
41+
Here, a domain will be defined as any connected set of elements in an $$n$$-dimensional space whose values do not vary beyond a pre-defined threshold.
42+
As an example, if we take a circle embedded into a 2-dimensional grid, we have 3 separate domains:
43+
1. Inside the circle where all elements are 0.
44+
2. The circle, itself, where the elements are set to 0.75.
45+
3. Outside the circle where all elements are similarly 0.
46+
47+
<p>
48+
<img class="center" src="res/simple_circle.png" style="width:70%" />
49+
</p>
50+
51+
Though there are some more complicated ways to determine the extents of the domain, we will not focus on this aspect of the flood fill method for the remainder of this chapter and instead leave it for subsequent chapters.
52+
So now we will focus on the process of walking through each element in the domain and changing some property.
53+
54+
## Domain traversal
55+
56+
As before, the simplest example to work with is that of an image, where each element in our domain is a single pixel.
57+
Here, we can connect each pixel to all other pixels in its vicinity, like so:
58+
59+
<p>
60+
<img class="center" src="res/grid_1.png" style="width:70%" />
61+
</p>
62+
63+
In this image, a border is shown between each individual pixel and a grid is superimposed to show how each pixel is connected to its neighbors.
64+
This means that each element has 4 neighbors: north, south, east, and west.
65+
We could also include northeast, southeast, southwest, and northwest if we wanted to do an 8-way fill, but we will restrict the discussion to the 4-way fill for now, as the method is essentially the same and slightly easier to understand with fewer elements to worry about.
66+
67+
By connecting each pixel to its neighbors in this way, the flood fill operation becomes a process of graph traversal, not too dissimilar from the [tree traversal](../tree_traversal/tree_traversal.md) methods described before.
68+
This means that after selecting our initial location, we can then traverse through all elements in either a depth-first or breadth-first fashion.
69+
We will be covering the following this chapter:
70+
71+
1. Finding all neighbours
72+
2. Depth-first node traversal
73+
3. Breadth-first node traversal and small-scale optimizations
74+
75+
So let's start by discussing how we might go about finding the neighbors to fill.
76+
77+
### Finding all neighbors
78+
79+
The first step of this method is to query the location of all possible neighbors.
80+
At first glance, this seems rather straightforward.
81+
One simply needs to look up, down, left, and right of the current location and add those elements to the list of neighbors if they are:
82+
83+
1. On the canvas
84+
2. Have a value *close enough* to the old value we would like to replace
85+
86+
In code, this might look like this:
87+
88+
{% method %}
89+
{% sample lang="jl" %}
90+
[import:37-55, lang:"julia"](code/julia/flood_fill.jl)
91+
{% endmethod %}
92+
93+
94+
This code is set up to return a vector of elements to then use for subsequent sections.
95+
96+
### Depth-first node traversal
97+
98+
Now that we have the ability to find all neighboring elements, we can proceed to traverse through those nodes in the most straightforward way: recursion.
99+
100+
In code, it might look like this:
101+
102+
{% method %}
103+
{% sample lang="jl" %}
104+
[import:106-118, lang:"julia"](code/julia/flood_fill.jl)
105+
106+
All Julia code snippets for this chapter rely on an exterior `color!(...)` function, defined as
107+
108+
[import:23-35, lang:"julia"](code/julia/flood_fill.jl)
109+
{% endmethod %}
110+
111+
The above code continues recursing through available neighbors as long as neighbors exist, and this should work so long as we are adding the correct set of neighbors.
112+
113+
Additionally, it is possible to do the same type of traversal by managing a stack, like so:
114+
115+
{% method %}
116+
{% sample lang="jl" %}
117+
[import:57-77, lang:"julia"](code/julia/flood_fill.jl)
118+
{% endmethod %}
119+
120+
This is ultimately the same method of traversal as before; however, because we are managing our own data structure, there are a few distinct differences:
121+
1. The manually managed stack could be slightly slower and potentially more memory-intensive
122+
2. It is easy to reach the maximum recursion depth on certain hardware with the recursive method, so it is best to use the stack-based implementation in those cases.
123+
124+
If we were to use either of these methods to fill a circle embedded in a two dimensional domain, we would see the following
125+
126+
<div style="text-align:center">
127+
<video style="width:70%" controls loop>
128+
<source src="res/recurse_animation.mp4" type="video/mp4">
129+
Your browser does not support the video tag.
130+
</video>
131+
</div>
132+
133+
Here, we see that these methods will traverse through one direction first before filling from there.
134+
This is potentially the easiest method to write, but it is not the most intuitive fill pattern.
135+
I suspect that if someone was asked to fill the contents of the circle on their own, they would fill it more evenly from the center, like so:
136+
137+
<div style="text-align:center">
138+
<video style="width:70%" controls loop>
139+
<source src="res/queue_animation.mp4" type="video/mp4">
140+
Your browser does not support the video tag.
141+
</video>
142+
</div>
143+
144+
This is simply another traversal strategy known as breadth-first traversal and comes with its own set of caveats.
145+
We will discuss this further in the next subsection
146+
147+
### Breadth-first node traversal and small-scale optimizations
148+
149+
Breadth-first node traversal is as simple as switching the stack in the depth-first strategy with a queue.
150+
The code would look something like this:
151+
152+
{% method %}
153+
{% sample lang="jl" %}
154+
[import:80-104, lang:"julia"](code/julia/flood_fill.jl)
155+
{% endmethod %}
156+
157+
Now, there is a small trick in this code that must be considered to make sure it runs optimally.
158+
Namely, the nodes must be colored *when they are being enqueued*, not when visiting the node.
159+
At least for me, it was not immediately obvious why this would be the case, but let me try to explain.
160+
161+
Let's imagine that we decided to write code that colored all neighboring nodes only when visiting them.
162+
When querying all possible neighbors, we will add 4 elements to the queue for the north, south, east, and west neighbors of the initial node, as shown below:
163+
164+
<p>
165+
<img class="center" src="res/grid_2.png" style="width:70%" />
166+
</p>
167+
168+
Now let's imagine we travel east first.
169+
It then enqueues three more nodes: north, south, and east again.
170+
This is shown below:
171+
172+
<p>
173+
<img class="center" src="res/grid_3.png" style="width:70%" />
174+
</p>
175+
176+
It does not enqueue its west neighbour because this has already been colored.
177+
At this stage, we will have six nodes ready to be colored and 2 that are already colored.
178+
Now let's say we travel north next.
179+
This node will enqueue three more nodes: west, north, and east, as shown below:
180+
181+
<p>
182+
<img class="center" src="res/grid_4.png" style="width:70%" />
183+
</p>
184+
185+
The problem is that the east element has *already been enqueued for coloring by the previous node*!.
186+
This shared element is colored in red.
187+
As we progress through all four initial neighbours, we will find 4 nodes that are doubly enqueued: all directions diagonal to the initial location!
188+
This is again shown below:
189+
190+
<p>
191+
<img class="center" src="res/grid_5.png" style="width:70%" />
192+
</p>
193+
194+
As the number of nodes increases, so does the number of duplicate nodes.
195+
A quick fix is to color the nodes *when they are being enqueud* like in the example code above.
196+
When doing this, duplicates will not be enqueued with a breadth-first scheme because they will already be colored when other nodes are trying to find their neighbors.
197+
This created a node connection pattern like so:
198+
199+
<p>
200+
<img class="center" src="res/grid_6.png" style="width:70%" />
201+
</p>
202+
203+
As some final food for thought: why wasn't this a problem with the depth-first strategy?
204+
The simple answer is that it actually was an issue, but it was way less prevalent.
205+
With the depth-first strategy, a number of unnecessary nodes are still pushed to the stack, but because we consistently push one direction before spreading out to other directions, it is more likely that the nodes have filled neighbours when they are looking for what to fill around them.
206+
207+
Simply put: depth-first traversal is slightly more efficient in this case unless you can color as querying for neighbors, in which case breadth-first is more efficient.
208+
209+
## Conclusions
210+
211+
As stated before, the method discussed in this chapter is just the tip of the iceberg and many other flood fill methods exist that are likely to be more efficient for most purposes.
212+
These will all be covered in subsequent chapters which will come out somewhat regularly throughout the next few months, lest we flood that archive with flood fill methods.
213+
214+
## Example Code
215+
216+
The example code for this chapter will be the simplest application of flood fill that still adequately tests the code to ensure it is stopping at boundaries appropriately.
217+
For this, we will create a two dimensional array of floats, all starting at 0.0, and then set a single vertical line of elements at the center to be 1.0.
218+
After, we will fill in the left-hand side of the array to be all ones by choosing any point within the left domain to fill.
219+
220+
{% method %}
221+
{% sample lang="jl" %}
222+
[import, lang:"julia"](code/julia/flood_fill.jl)
223+
{% endmethod %}
224+
225+
226+
### Bibliography
227+
228+
{% references %} {% endreferences %}
229+
230+
<script>
231+
MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
232+
</script>
233+
234+
## License
235+
236+
##### Code Examples
237+
238+
The code examples are licensed under the MIT license (found in [LICENSE.md](https://github.com/algorithm-archivists/algorithm-archive/blob/master/LICENSE.md)).
239+
240+
##### Text
241+
242+
The text of this chapter was written by [James Schloss](https://github.com/leio) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
243+
244+
[<p><img class="center" src="../cc/CC-BY-SA_icon.svg" /></p>](https://creativecommons.org/licenses/by-sa/4.0/)
245+
246+
##### Images/Graphics
247+
- The image "[Example Bucket Fill](res/example.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
248+
- The image "[Circle Domains](res/simple_circle.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
249+
- The image "[Grid 1](res/grid_1.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
250+
- The image "[Grid 2](res/grid_2.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
251+
- The image "[Grid 3](res/grid_3.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
252+
- The image "[Grid 4](res/grid_4.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
253+
- The image "[Grid 5](res/grid_5.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
254+
- The image "[Grid 6](res/grid_6.png)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
255+
- The video "[Stack Fill](res/recurse_animation.mp4)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
256+
- The video "[Queue Fill](res/queue_animation.mp4)" was created by [James Schloss](https://github.com/leios) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
257+
258+

‎contents/flood_fill/res/example.png

24.3 KB
Loading
+23
Loading

‎contents/flood_fill/res/grid_1.png

30.2 KB
Loading

‎contents/flood_fill/res/grid_2.png

29.9 KB
Loading

‎contents/flood_fill/res/grid_3.png

29.9 KB
Loading

‎contents/flood_fill/res/grid_4.png

62.8 KB
Loading

‎contents/flood_fill/res/grid_5.png

62.4 KB
Loading

‎contents/flood_fill/res/grid_6.png

30 KB
Loading
133 KB
Binary file not shown.
124 KB
Binary file not shown.
32 KB
Loading

‎literature.bib

+18
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,21 @@ @article{lundmark2004
261261
year={2004}
262262
}
263263

264+
#------------------------------------------------------------------------------#
265+
# Flood Fill
266+
#------------------------------------------------------------------------------#
267+
268+
@misc{gimp_bucket,
269+
title={Bucket Fill in Gimp},
270+
url={https://docs.gimp.org/2.10/en/gimp-tool-bucket-fill.html},
271+
year={2020}
272+
}
273+
274+
@book{torbert2016,
275+
title={Applied computer science},
276+
author={Torbert, Shane},
277+
year={2016},
278+
pages={158},
279+
publisher={Springer}
280+
}
281+

0 commit comments

Comments
 (0)
Please sign in to comment.