Skip to content
Open
3 changes: 2 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
name = "ImageDraw"
uuid = "4381153b-2b60-58ae-a1ba-fd683676385f"
version = "0.2.2"
version = "0.2.3"

[deps]
Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"
Distances = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7"
ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Expand Down
8 changes: 7 additions & 1 deletion src/ImageDraw.jl
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
module ImageDraw

# package code goes here
using ImageCore, Distances
using ImageCore, Distances, Combinatorics

include("core.jl")
include("line2d.jl")
include("ellipse2d.jl")
include("circle2d.jl")
include("paths.jl")
include("cross.jl")
include("background.jl")

#export methods
export
draw,
draw!,
generatecanvas,
bresenham,
xiaolin_wu

Expand All @@ -22,6 +24,10 @@ export
#Drawable
Drawable,

#backgrounds
SolidBackground,
StripedBackground,

#Point
Point,

Expand Down
64 changes: 64 additions & 0 deletions src/background.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""
gen_img = generatecanvas(colortype, x, y)
gen_img = generatecanvas(colortype, x, y, background)
gen_img = generatecanvas(colortype, [x,y])
gen_img = generatecanvas(colortype, [x,y], background)

Creates an image of with the given colortype and size.
Defaults to a black solid image
"""
generatecanvas(colortype::Type, x::Int, y::Int) = zeros(colortype, (x,y))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since the work is being done by zeros, why do we effectively have to create an alias to it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You're right. I have done this a different way. We can scrap this merge request. We can stick a stripped function in when needed.


generatecanvas(colortype::Type, size::NTuple{2, Int}) = zeros(colortype, size)

generatecanvas(colortype::Type, x::Int, y::Int, b::AbstractBackground) =
generatecanvas(colortype, (x,y), b)

function generatecanvas(colortype::Type, size::NTuple{2, Int}, b::AbstractBackground)
draw!(zeros(colortype, (size...)), b)
end


"""
new_img = draw!(img, solid_background)

Paints the entire given image a solid color
"""
function draw!(img::AbstractArray{T,2}, b::SolidBackground) where {T<:Colorant}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Again seems redundant with standard names:

julia> A = fill(colorant"red", 2, 2)
2×2 Array{RGB{N0f8},2} with eltype RGB{Normed{UInt8,8}}:
 RGB{N0f8}(1.0,0.0,0.0)  RGB{N0f8}(1.0,0.0,0.0)
 RGB{N0f8}(1.0,0.0,0.0)  RGB{N0f8}(1.0,0.0,0.0)

julia> fill!(A, colorant"blue")
2×2 Array{RGB{N0f8},2} with eltype RGB{Normed{UInt8,8}}:
 RGB{N0f8}(0.0,0.0,1.0)  RGB{N0f8}(0.0,0.0,1.0)
 RGB{N0f8}(0.0,0.0,1.0)  RGB{N0f8}(0.0,0.0,1.0)

b.color == zero(typeof(b.color)) ? (return img) : nothing
x, y = size(img)
for i = 1:y
for j = 1:x
draw!(img, Point(j, i), b.color)
end
end
img
end

"""
new_img = draw!(img, striped_background)

Layers colors onto a given image to set a "background"
"""
function draw!(img::AbstractArray{T,2}, b::StripedBackground) where {T<:Colorant}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This one is interesting and adds novel functionality. However, you seem to implicitly assume that img is an all-zeros array, in which case maybe this shouldn't take img as an argument? Should we have a function called stripes?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also worth asking: how often do you use angles different from 0 and π/2? If your stripes are all rectilinear then there are easier implementations.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I was more trying to make it completely general. A stripes function might be preferable and not dealing with backgrounds. Its just fit better into the flow of the module I am making.

x,y = size(img)
for (e,(c,d)) in enumerate(zip(b.colors,b.distances))
draw!(img, LineNormal(d,b.θ), c)
for i = 1:y
cnt = 0
for j = 1:x
if img[i,j] == zero(typeof(c))
draw!(img, Point(j,i), c)
cnt += 1
elseif img[i,j] == c
break
elseif img[i,j] in b.colors[1:e-1]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Slow. How about an auxiliary array that indicates whether a given pixel has already been handled?

cnt = 1
continue
end
end
cnt == 0 ? break : continue
end
end
img
end
89 changes: 74 additions & 15 deletions src/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,39 @@ struct Point <: Drawable
y::Int
end

abstract type Line <: Drawable end
abstract type Circle <: Drawable end
abstract type AbstractPath <: Drawable end
Comment thread
Codyk12 marked this conversation as resolved.
abstract type AbstractLine <: Drawable end
abstract type AbstractShape <: Drawable end
abstract type AbstractBackground <: Drawable end


abstract type AbstractPolygon <: AbstractShape end
abstract type AbstractEllipse <: AbstractShape end
abstract type AbstractCircle <: AbstractEllipse end

"""
background = SolidBackground(color)

A `Drawable` background that will fill the 'background' of an image with
the set color
"""

struct SolidBackground{T<:Colorant} <: AbstractBackground
color::T
end

"""
background = StripedBackground(color)

A `Drawable` background that will fill the 'background' of an image with
the given colors at the intervals given at the given angle
"""

struct StripedBackground{T<:Colorant, U<:Real, V<:Real} <: AbstractBackground
colors::Vector{T}
distances::Vector{U}
θ::V
end


"""
Expand All @@ -24,7 +55,7 @@ abstract type Circle <: Drawable end
A `Drawable` infinite length line passing through the two points
`p1` and `p2`.
"""
struct LineTwoPoints <: Line
struct LineTwoPoints <: AbstractLine
p1::Point
p2::Point
end
Expand All @@ -36,7 +67,7 @@ A `Drawable` infinte length line having perpendicular length `ρ` from
origin and angle `θ` between the perpendicular and x-axis

"""
struct LineNormal{T<:Real, U<:Real} <: Line
struct LineNormal{T<:Real, U<:Real} <: AbstractLine
ρ::T
θ::U
end
Expand All @@ -46,7 +77,7 @@ end

A `Drawable` circle passing through points `p1`, `p2` and `p3`
"""
struct CircleThreePoints <: Circle
struct CircleThreePoints <: AbstractCircle
p1::Point
p2::Point
p3::Point
Expand All @@ -57,7 +88,7 @@ end

A `Drawable` circle having center `center` and radius `ρ`
"""
struct CirclePointRadius{T<:Real} <: Circle
struct CirclePointRadius{T<:Real} <: AbstractCircle
center::Point
ρ::T
end
Expand All @@ -67,7 +98,7 @@ end

A `Drawable` finite length line between `p1` and `p2`
"""
struct LineSegment <: Drawable
struct LineSegment <: AbstractLine
p1::Point
p2::Point
end
Expand All @@ -80,7 +111,7 @@ of points in `[point]`.
!!! note
This will create a non-closed path. For a closed path, see `Polygon`
"""
struct Path <: Drawable
struct Path <: AbstractPath
vertices::Vector{Point}
end

Expand All @@ -90,7 +121,7 @@ end
A `Drawable` ellipse with center `center` and parameters `ρx` and `ρy`

"""
struct Ellipse{T<:Real, U<:Real} <: Drawable
struct Ellipse{T<:Real, U<:Real} <: AbstractEllipse
center::Point
ρx::T
ρy::U
Expand All @@ -104,7 +135,7 @@ consecutive points in `[vertex]` along with the first and last point.
!!! note
This will create a closed path. For a non-closed path, see `Path`
"""
struct Polygon <: Drawable
struct Polygon <: AbstractPolygon
vertices::Vector{Point}
end

Expand All @@ -120,7 +151,7 @@ A `Drawable` regular polygon.
* `θ::Real` : orientation of the polygon w.r.t x-axis (in radians)

"""
struct RegularPolygon{T<:Real, U<:Real} <: Drawable
struct RegularPolygon{T<:Real, U<:Real} <: AbstractPolygon
center::Point
side_count::Int
side_length::T
Expand All @@ -131,7 +162,7 @@ end
cross = Cross(c, range::UnitRange{Int})
A `Drawable` cross passing through the point `c` with arms ranging across `range`.
"""
struct Cross <: Drawable
struct Cross <: AbstractPath
c::Point
range::UnitRange{Int}
end
Expand Down Expand Up @@ -186,10 +217,9 @@ function draw!(img::AbstractArray{T,2}, point::Point, color::T) where T<:Coloran
end

"""

img_new = drawifinbounds!(img, y, x, color)
img_new = drawifinbounds!(img, Point, color)
img_new = drawifinbounds!(img, CartesianIndex, color)
img_new = drawifinbounds!(img, point, color)
img_new = drawifinbounds!(img, cartesianIndex, color)

Draws a single point after checkbounds() for coordinate in the image.
Color Defaults to oneunit(T)
Expand All @@ -203,3 +233,32 @@ function drawifinbounds!(img::AbstractArray{T,2}, y::Int, x::Int, color::T) wher
if checkbounds(Bool, img, y, x) img[y, x] = color end
img
end

"""
Comment thread
Codyk12 marked this conversation as resolved.
img_new = drawwiththickness!(img, y, x, color, thickness)
img_new = drawwiththickness!(img, point, color, thickness)
img_new = drawwiththickness!(img, cartesianIndex, color, thickness)

Draws pixel with given thickness
Color Defaults to oneunit(T)
Thickness defaults to 1

"""

drawwiththickness!(img::AbstractArray{T,2}, p::Point, color::T = oneunit(T), thickness::Int=1) where {T<:Colorant} = drawwiththickness!(img, p.y, p.x, color, thickness)
drawwiththickness!(img::AbstractArray{T,2}, p::CartesianIndex{2}, color::T = oneunit(T), thickness::Int=1) where {T<:Colorant} = drawifinbounds!(img, Point(p), color, thickness)

function drawwiththickness!(img::AbstractArray{T,2}, y0::Int, x0::Int, color::T, thickness::Int) where {T<:Colorant}
n = Int(round(thickness / 2))
evn = thickness % 2 == 1 ? 0 : 1
pixels = [i for i = -(n-evn):n]

for (x,y) in Combinatorics.combinations(pixels, 2)
drawifinbounds!(img, y0+y, x0+x, color)
drawifinbounds!(img, y0+y, x0-x, color)
drawifinbounds!(img, y0-y, x0+x, color)
drawifinbounds!(img, y0-y, x0-x, color)
end
drawifinbounds!(img, y0, x0, color)
img
end
6 changes: 3 additions & 3 deletions src/cross.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ A `Drawable` cross passing through the point `c` with arms that are `arm` pixels
"""
Cross(c, arm::Int) = Cross(c, -arm:arm)

function draw!(img::AbstractArray{T, 2}, cross::Cross, color::T) where T<:Colorant
function draw!(img::AbstractArray{T, 2}, cross::Cross, color::T, thickness::Int=1) where T<:Colorant
Comment thread
Codyk12 marked this conversation as resolved.
for Δx in cross.range
drawifinbounds!(img, cross.c.y, cross.c.x + Δx, color)
drawwiththickness!(img, cross.c.y, cross.c.x + Δx, color, thickness)
end
for Δy in cross.range
drawifinbounds!(img, cross.c.y + Δy, cross.c.x, color)
drawwiththickness!(img, cross.c.y + Δy, cross.c.x, color, thickness)
end
img
end
Loading