Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Flood Fill Implementation #60

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,55 @@ for p in (p1,p2,p3)
end
save("images/lighthouse_linesegment.png", img); nothing # hide
```

![](images/lighthouse_linesegment.png)

```@raw html
<img src="images/lighthouse_cross.png" width="512px" alt="edge detection demo 1 image" />
<p>
```

Filling Polygon with Boundary Fill Algorithm

```@example usage
using TestImages, ImageDraw, ColorVectorSpace
using FileIO # hide
img1 = testimage("lighthouse")
img2 = testimage("lighthouse")

draw!(img1, Ellipse(CirclePointRadius(400, 200, 100; thickness = 50, fill = false)))
draw!(img1, Polygon(RectanglePoints(Point(10, 10), Point(100, 100))), RGB{N0f8}(1))

verts = [CartesianIndex(1, 1)]

draw!(img2, Ellipse(CirclePointRadius(400, 200, 100; thickness = 50, fill = false)))
draw!(img2, verts, BoundaryFill(400, 200; fill_value = RGB(0), boundary_value = RGB(1)); closed = false)

draw!(img2, Polygon(RectanglePoints(Point(10, 10), Point(100, 100))), RGB{N0f8}(1))
draw!(img2, verts, BoundaryFill(50, 50; fill_value = RGB(0), boundary_value = RGB(1)); closed = false)

mosaicview(img1, img2; nrow=1)
```

Filling Polygon using Flood Fill Algorithm

```@example usage
using TestImages, ImageDraw, ColorVectorSpace
using FileIO # hide
img1 = testimage("lighthouse")
img2 = testimage("lighthouse")

draw!(img1, Ellipse(CirclePointRadius(400, 200, 100; thickness = 50, fill = false)))
draw!(img1, Polygon(RectanglePoints(Point(10, 10), Point(100, 100))), RGB{N0f8}(1))

verts = [CartesianIndex(1, 1)]

draw!(img2, Polygon(RectanglePoints(Point(10, 10), Point(100, 100))), RGB{N0f8}(1))
draw!(img2, verts, FloodFill(10, 10; fill_value = RGB(0.0), current_value = RGB(1.0)); closed = false)

draw!(img2, Ellipse(CirclePointRadius(400, 200, 100; thickness = 50, fill = false)))
draw!(img2, verts, FloodFill(320, 200; fill_value = RGB(0.0), current_value = RGB(1.0)); closed = false)

mosaicview(img1, img2; nrow=1)

```

5 changes: 3 additions & 2 deletions src/ImageDraw.jl
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export
Cross,

#Polygon fill
BoundaryFill

BoundaryFill,
FloodFill

end # module
75 changes: 74 additions & 1 deletion src/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,79 @@ BoundaryFill(x::Int = 1, y::Int = 1; fill_value::Colorant = RGB(1), boundary_val
BoundaryFill(p::CartesianIndex{2}; fill_value::Colorant = RGB(1), boundary_value::Colorant = fill_value) = BoundaryFill(p[1], p[2], fill_value, boundary_value)
BoundaryFill(p::Point; fill_value::Colorant = RGB(1), boundary_value::Colorant = fill_value) = BoundaryFill(p.y, p.x, fill_value, boundary_value)

"""
FloodFill{T<:Colorant} <: AbstractPolyFillAlgorithm
FloodFill( x::Int, y::Int, fill_value::T, boundary_value::T)

draw(img, verts, alg::FloodFill; closed)
draw!(img, verts, alg::FloodFill; closed)

Applies flood fill algortithm on image provided by user. Flood fill is an algorithm that determines and alters
the area connected to a given node in a multi-dimensional array with some matching attribute. In this case,
the matching attribute is the color of a region i.e. `current_value` and it's replaced by `fill_value`.

It's like the paint program utility in which when we want to change color of a particular region with color
`current_value` to a different selected color `fill_value`.

# Output

Return the flood filled image inside the region.

# Arguments

## `x` && `y`

Flood fill is a seeded polygon filling algorithm.
So we need to provide the seed point (x,y) inside the image from where the algorithm can start its function.

## `fill_value`

The pixels inside the region with color `current_value` will be changed using this parameter. This parameter is
used also when vertices of polygon are connected using `current_value = true`.

## `current_value`

The original color of a particular region that is to be changed. `current_value` is replaced by `fill_value`.

# `closed`

`closed` keyword specifies whether to connect the polygon edges using the verts provided.
Edges will be filled and connected using `fill_value`.

# `Example`

```julia
using ImageDraw

verts = [CartesianIndex(3, 3), CartesianIndex(3, 8), CartesianIndex(8, 8), CartesianIndex(8, 3), CartesianIndex(3,3)]

img = rand(RGB, 10, 10)
img[:,:] .= RGB(0.5)

img[3:8, 3] .= RGB(1.0)
img[3:8, 8] .= RGB(1.0)
img[3, 3:8] .= RGB(1.0)
img[8, 3:8] .= RGB(1.0)

draw!(img, verts, FloodFill(3, 3; fill_value = RGB(0.0), current_value = img[3, 3]); closed = false)
```
"""
struct FloodFill{T<:Colorant} <: AbstractPolyFillAlgorithm
x::Int
y::Int
fill_value::T
current_value::T
function FloodFill(x::Int, y::Int, fill_value::T, current_value::T) where {T <: Colorant}
new{T}(x, y, fill_value, current_value)
end

end

FloodFill(x::Int = 1, y::Int = 1; fill_value::Colorant = RGB(1.0), current_value::Colorant = fill_value) = FloodFill(x, y, fill_value, current_value)
FloodFill(p::CartesianIndex{2}; fill_value::Colorant = RGB(1.0), current_value::Colorant = fill_value) = FloodFill(p[1], p[2], fill_value, current_value)
FloodFill(p::Point; fill_value::Colorant = RGB(1.0), current_value::Colorant = fill_value) = FloodFill(p.y, p.x, fill_value, current_value)


"""
rectangle = RectanglePoints(p1, p2)
rectangle = RectanglePoints(x1, y1, x2, y2)
Expand Down Expand Up @@ -321,7 +394,7 @@ Draws the `drawable` object on a copy of image `img` using color
`color`. Can also draw multiple `Drawable` objects when passed
as a `AbstractVector{Drawable}` with corresponding colors in `[color]`
"""
draw(img::AbstractArray{T,2}, args...) where {T<:Colorant} = draw!(copy(img), args...)
draw(img::AbstractArray{T}, args...; kwargs...) where {T<:Colorant} = draw!(copy(img), args...; kwargs...)

Point(τ::Tuple{Int, Int}) = Point(τ...)
Point(p::CartesianIndex) = Point(p[2], p[1])
Expand Down
26 changes: 23 additions & 3 deletions src/polygonfill2d.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
"""

function (f::BoundaryFill)(
res::AbstractArray{T,2},
verts::Vector{CartesianIndex{2}},
res::AbstractArray{T, 2},
verts::Vector{CartesianIndex{N}},
x::Int,
y::Int,
) where {T<:Colorant}
) where {T <: Colorant, N}
if checkbounds(Bool, res, y, x)
if (res[y, x] != f.boundary_value && res[y, x] != f.fill_value)
res[y, x] = f.fill_value
Expand All @@ -22,6 +22,26 @@ function (f::BoundaryFill)(
res
end

"""
(f::FloodFill)(res::AbstractArray{T,2}, verts::Vector{CartesianIndex{2}}, x::Int, y::Int, fill_value::T, boundary_value::T) where {T <: Colorant}

"""

function (f::FloodFill)(
res::AbstractArray{T, 2},
verts::Vector{CartesianIndex{N}},
x::Int,
y::Int,
) where {T<:Colorant, N}
if (res[y, x] != f.current_value || res[y, x] == f.fill_value) return res end
if checkbounds(Bool, res, y, x) res[y, x] = f.fill_value end
f(res, verts, x + 1, y)
f(res, verts, x - 1, y)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can still be {T, 2} as the implementation is specified for matrix; this won't affect the method ambiguity on draw so no need to change.

f(res, verts, x, y + 1)
f(res, verts, x, y - 1)
res
end

"""
draw!(img::AbstractArray{T,2}, verts::Vector{CartesianIndex{2}}, f::AbstractPolyFillAlgorithm; closed::Bool)

Expand Down
Loading