Drawing Package for JuliaImages
juliaimages / imagedraw.jl Goto Github PK
View Code? Open in Web Editor NEWDrawing Package for JuliaImages
License: Other
Drawing Package for JuliaImages
License: Other
Hi! i'm now planning to add logo images to ImageDraw.jl.
using Images, ImageDraw
# original image
img = load(download("https://raw.githubusercontent.com/JuliaImages/juliaimages.github.io/source/docs/src/assets/logo.png"))
# Define some colors
c_r, c_g, c_b, c_p = RGBA.([Colors.JULIA_LOGO_COLORS...])
gray_dark = RGBA{N0f8}(0.1,0.1,0.1)
gray_light = RGBA{N0f8}(0.9,0.9,0.9)
# change base corlor black to gray
img = img*N0f8(0.5) .+ RGBA{N0f8}(0.5,0.5,0.5,0)
img_dark = copy(img)
img_light = copy(img)
# define points on the image
p_green = Point(87,35)
p_red = Point(75,55)
p_purple = Point(99,55)
p1 = Point(13,96)
p2 = Point(52,50)
p3 = Point(73,73)
p4 = Point(84,62)
p5 = Point(116,97)
for (img,gray,name) in ((img_dark,gray_light,"logo-dark.png"), (img_light,gray_dark,"logo.png"))
draw!(img, LineTwoPoints(p1,p2), gray)
draw!(img, LineTwoPoints(p2,p3), gray)
draw!(img, LineTwoPoints(p3,p4), gray)
draw!(img, LineTwoPoints(p4,p5), gray)
draw!(img, Cross(p1,8), gray)
draw!(img, Cross(p2,8), gray)
draw!(img, Cross(p3,8), gray)
draw!(img, Cross(p4,8), gray)
draw!(img, Cross(p5,8), gray)
draw!(img, Ellipse(CirclePointRadius(p_green,10)), c_g)
draw!(img, Ellipse(CirclePointRadius(p_red,10)), c_r)
draw!(img, Ellipse(CirclePointRadius(p_purple,10)), c_p)
save(name,img)
end
Do you have any comments?
The current ImageDraw design has significantly restricted its possibility to the 2D case, and that's not good in general (even though it's the most common case).
What I have in mind is:
abstract type Drawable end
struct Point{C<:Colorant, N} <: Drawable
pos::CartesianIndex{N}
color::C
thickness::Int
end
struct Polygon{C<:Colorant, N, T<:CartesianIndex} <: Drawable
vertices::NTuple{N, T}
border_color::C
border_width::Int
fill_color::C
fill::Bool # whether we need to fill the region with color C
end
struct Layers{A:: PriorityQueue} <: Drawable
objects::A
end
Basically, each drawable type consists of the whole information on how it can be drawn.
Layers
is a simple wrapper on DataStructures.PriortyQueue
that controls how individual objects are drawn in one draw
call.
Point
is not serving as the index, but instead as a real physical "point" with color and thickness information.
This is a draft type design, we could definitely add more information into each drawable struct.
Does not compile with Julia v1.0
PR #3 will handle general circle/ellipse drawing. Implementations of circle/ellipse perimeter drawing like the bresenham algorithm still need to be implemented as they are faster.
using ImageCore, ImageShow, TestImages, ImageDraw
img = testimage("lighthouse")
verts = [CartesianIndex(2, 2), CartesianIndex(2, 6), CartesianIndex(6, 2)]
alg = BoundaryFill(3, 3; fill_value = RGB{N0f8}(1), boundary_value = RGB{N0f8}(1))
draw(img, verts, alg; closed=false)
The recursive call of BoundaryFill
simply blows up the call stack:
ImageDraw.jl/src/polygonfill2d.jl
Lines 14 to 20 in 54f3cfe
cc: @ashwani-rathee
There are some docs for this package: https://github.com/JuliaImages/ImageDraw.jl/blob/master/docs/src/index.md
But the docs link in README is broken
Not required before the end of GSoC, but ideally we'd get this package documented on the JuliaImages site.
These two drawables will also be quite useful addition to the project in 2d drawing methods
What do you people think?
References:
I'm not sure if this is an expected behavior or not:
img = zeros(RGB{Float64}, 10, 10)
verts = [CartesianIndex(2, 2), CartesianIndex(2, 6), CartesianIndex(6, 2), CartesianIndex(2,2)]
alg = BoundaryFill(4, 4; fill_value = RGB(1), boundary_value = RGB(1))
mosaic(
draw(img, verts, alg; closed=true),
draw(img, verts, alg; closed=false);
npad=2, fillvalue=0.5
)
closed=false
is an all-white image:
cc: @ashwani-rathee
It would be nice to allow fill the region with multiple fillvalues:
etp
hereusing Interpolations, ImageCore, OffsetArrays, ImageShow
make_circle(r, fillvalues; kwargs...) = make_circle(eltype(fillvalues), r, fillvalues; kwargs...)
function make_circle(T, r, fillvalues::Vector)
etp = extrapolate(interpolate(fillvalues, BSpline(Linear())), Line())
sz = (2r+1, 2r+1)
canvas = OffsetArrays.centered(Array{T, 2}(undef, sz))
for i in CartesianIndices(canvas)
d = sqrt(mapreduce(abs2, +, i.I))
canvas[i] = etp(d)
end
return canvas
end
img = mosaic(
mosaic(
make_circle(256, collect(0:0.005:0.8)),
make_circle(256, repeat(collect(0:0.002:0.8))),
make_circle(256, repeat(collect(0:0.05:0.8), inner=30));
rowmajor=true, nrow=1
),
mosaic(
make_circle(256, colormap("Blues")),
make_circle(256, colormap("Blues", 256)),
make_circle(256, colormap("Blues", 512));
rowmajor=true, nrow=1
)
)
Drawing ellipses and paths can produce out of bound errors. I am working on something else a.t.m but this is what fixed it for me:
function setifinbounds!_(A::AbstractArray{T,2}, i, j, c::T) where {T}
if checkbounds(Bool, A, i, j)
@inbounds A[i, j] = c
end
c
end
function draw!(img::AbstractArray{T, 2}, ellipse::Ellipse, color::T) where T<:Colorant
ps = Tuple{Int,Int}[]
for i in ellipse.center.y : ellipse.center.y + ellipse.ρy
for j in ellipse.center.x : ellipse.center.x + ellipse.ρx
val = ((i - ellipse.center.y) / ellipse.ρy) ^ 2 + ((j - ellipse.center.x) / ellipse.ρx) ^ 2
if val < 1
push!(ps, (i, j))
end
end
end
for (yi, xi) in ps
setifinbounds!_(img, yi, xi, color)
setifinbounds!_(img, 2 * ellipse.center.y - yi, xi, color)
setifinbounds!_(img, yi, 2 * ellipse.center.x - xi, color)
setifinbounds!_(img, 2 * ellipse.center.y - yi, 2 * ellipse.center.x - xi, color)
end
img
end
function draw!(img::AbstractArray{T, 2}, path::Path, color::T) where T<:Colorant
vertices = [CartesianIndex(p.y, p.x) for p in path.vertices]
f = CartesianIndex(map(r->first(r)-1, indices(img)))
l = CartesianIndex(map(r->last(r), indices(img)))
inrange1 = min(f, vertices[1])==f && max(l, vertices[1])==l
for i in 1:length(vertices)-1
inrange2 = min(f,vertices[i+1])==f && max(l,vertices[i+1])==l
if inrange1 && inrange2
draw!(img, LineSegment(vertices[i], vertices[i+1]), color)
end
inrange1 = inrange2
end
end
There is a method for making a line in ImageDraw.jl but the method for making a continued lines with multiple points given in arguments in not yet present.I believe single line isn't that useful and having ability to make poly line with continuous vertices/points mentioned in arguments could prove to be quite useful.Better yet if the polyline argument could be provided in vector or similar form.
This library provides many useful functions! It needs a (generated and linked) documentation website.
Hi, I'm running into a compatibility problem downstream where [email protected]
is definitely required but the latest available tag on ImageDraw.jl only has compat support for Distances up to v0.9. I see the CompatHelper PR for v0.10 was merged here in October #44 but has not made it into a stable tag just yet. Hence a request for a new patch release if at all possible please.
I needed those for example : Detection and measurement,indentification of circular objects in a image
Even though Luxor.jl provides functions for that,would we need it here?Just a simple circular,elliptical drawable is unintuitive when we explain more advanced ideas there
Idea: https://github.com/ashwani-rathee/Demos/blob/main/%F0%9F%8E%88%202.pdf
relevant issues:
JuliaImages/ImageEdgeDetection.jl#21
What I kind of need
@johnnychen94 What do you think?
When using CartesianIndex
to define a rectangle, the resulting rectangle is not bounded by the pixels mentioned in the CartesianIndex
.
Example:
julia> RectanglePoints(CartesianIndex(1,2), CartesianIndex(3,4))
RectanglePoints(Point(1, 2), Point(3, 4))
But Point(1, 2)
is the pixel with CartesianIndex(2,1)
.
The definition says:
RectanglePoints(p1::CartesianIndex{2}, p2::CartesianIndex{2}) = RectanglePoints(Point(p1[1], p1[2]), Point(p2[1], p2[2]))
To me it would make a lot more sense to define it as
RectanglePoints(p1::CartesianIndex{2}, p2::CartesianIndex{2}) = RectanglePoints(Point(p1[2], p1[1]), Point(p2[2], p2[1]))
Of course this would be breaking.
Am I just thinking about this the wrong way?
Other than colored shapes, it would be great if we could also add some text to the images.
I use this to color tracked paths in a composite image from time-lapse images, but I need to some how label individual tracks with an ID (like a bunch of numbers and/or letters).
Hmmm, maybe it would be easier to combine the image with vector graphics somehow.
ImageCore 0.9 is supported on master, but a release is needed
cc. @johnnychen94
eg:
img = zeros(RGB, (200,200));
draw(img, Ellipse(100, 100, 100, 100; thickness=29, fill=false), RGB(1,1,0))
will not draw anything because the code is using a bogus normalized distance for the inner ellipse.
Hi,
I was looking for implementation of a function, which would fill an image region based on the provided polygon. It exists in Matlab (poly2mask
) and also was re-implemented in python (see the discussion).
The implementation is almost trivial (see below), and the function seems generally useful. Would you be interested in me making a PR?
function point_in_polygon(poly_xs::Vector{T}, poly_ys::Vector{T}, x::T, y::T) where T<: Real
n_verts = length(poly_xs)
j = n_verts
c = false
for i in 1:n_verts
if (((poly_ys[i] <= y) && (y < poly_ys[j])) || ((poly_ys[j] <= y) && (y < poly_ys[i]))) &&
(x < (poly_xs[j] - poly_xs[i]) * (y - poly_ys[i]) / (poly_ys[j] - poly_ys[i]) + poly_xs[i])
c = !c
end
j = i
end
return c
end
function draw_polygon!(mask::Matrix{T2}, poly_xs::Vector{T}, poly_ys::Vector{T}, value::T2) where T<: Integer where T2 <: Real
min_x, max_x = max(minimum(poly_xs), 1), min(maximum(poly_xs), size(mask, 2))
min_y, max_y = max(minimum(poly_ys), 1), min(maximum(poly_ys), size(mask, 1))
for y in min_y:max_y
for x in min_x:max_x
if point_in_polygon(poly_xs, poly_ys, x, y)
mask[y, x] = value
end
end
end
end
function polygons_to_mask(polygons::Array{Matrix{T}, 1} where T <: Real, max_x::Int, max_y::Int)
poly_mask = zeros(Int, max_y, max_x);
for (i,p) in enumerate(polygons)
draw_polygon!(poly_mask, round.(Int, p[:,1]), round.(Int, p[:,2]), i)
end
return poly_mask
end
Hi all,
I understand that BoundaryFill recursively paints the pixels within a boundary. This recursive approach leads to stack overflow in my computer.
In my particular case, I computed a convex hull, converted it into an ImageDraw.Polygon
, and painted its outline with ImageDraw.draw!
:
using ColorTypes
ch_mask = zeros( Gray{Float32}, size(mask) )
draw!( ch_mask, ch_poly, Gray{Float32}(1) )
Using the centroid of the convex hull as a reference, I try to paint the interior with this code... but I consistently get a stack overflow error:
bf = ImageDraw.BoundaryFill( ch_centroid[2], ch_centroid[1], Gray{Float32}(1), Gray{Float32}(1) )
bf( ch_mask, [ CartesianIndex(0,0), ], ch_centroid[2], ch_centroid[1] )
This is how much was painted before the stack overflow occurred:
In case it helps, I ended up using connected components labelling from the outline of the convex hull to solve my problem, which generalizes to 3D and runs very fast:
using ImageComponentAnalysis
ch_mask_img = Float32.( ch_mask );
lbls = ImageComponentAnalysis.label_components( ch_mask_img .== 0 );
lbl_centroid = lbls[ ch_centroid... ]
Gray{Float32}.( lbls .== lbl_centroid )
i'm looking for a simple way to draw some text onto an existing array. is there an easy way to do this? cairo, luxor, compose, etc. all seem to output to files. i just want to modify the array i already have. thanks.
Just putting a reminder here. There is no hurry
I often need to mark something in the image without obscuring it so much. A cross does exactly that.
I'll therefor try to PR something like this:
struct Cross <: Drawable
c::Point
arm::Int
end
function draw!(img::AbstractArray{T, 2}, cross::Cross, color::T) where T<:Colorant
for x in -cross.arm:cross.arm
img[cross.c.y, cross.c.x + x] = color
end
for y in -cross.arm:cross.arm
img[cross.c.y + y, cross.c.x] = color
end
img
end
The example gives the impression that draw!
returns a copy like draw
img = testimage("lighthouse")
img_example_stage1 = draw!(img, Polygon(RectanglePoints(Point(10, 10), Point(100, 100))), RGB{N0f8}(1))
img_example_stage2 = draw!(img_example_stage1, Polygon(RectanglePoints(CartesianIndex(110, 10), CartesianIndex(200, 200))), RGB{N0f8}(1))
img_example_stage3 = draw!(img_example_stage2, Polygon(RectanglePoints(220, 10, 300, 300)), RGB{N0f8}(1))
save("images/lighthouse_rectangle.png", img); nothing # hide
Just a casual suggestion for the sole and maybe silly reason of consistency within the JuliaImages organisation.
Currently, there are some structural and naming issues in ImageDraw.jl
:
line
is a method for drawing a line while Line
is an abstract type denoting a line.line
used with LineTwoPoints
draws only a line segment while line
used with LineNormal
draws an infinite line on the image, which breaks consistency.line
, ellipse
, path
etc. do not clearly suggest whether they are methods to draw an object on an image or methods to create a new drawable object.I suggest the following changes in the API:
Line
, Ellipse
, Polygon
etc. should derive from Drawable
abstract type.draw
to draw Drawable
object(s) on an image(or multiple images). draw
would use multiple dispatch to draw the specific objects onto the image. This would produce a much neater API.RegularPolygon <: Drawable
to draw a regular polygon on an image. This could be used to define other shapes like Square
, EquilateralTriangle
etc.Line
would be used to draw infinite lines and LineSegment
would be used to draw a line segment of a specific length.Kindly suggest if these changes are relevant. If so, then I can start working on this.
This issue is used to trigger TagBot; feel free to unsubscribe.
If you haven't already, you should update your TagBot.yml
to include issue comment triggers.
Please see this post on Discourse for instructions and more details.
If you'd like for me to do this for you, comment TagBot fix
on this issue.
I'll open a PR within a few hours, please be patient!
Since this project is still WIP,these filling algorithms commonly used in computer graphics would be quite useful addition
References:
Looks like this package isn't maintained very actively; Several good feature PRs are pending, and docs are missing.
If currently nobody's interested in maintaining this, I'd like to take the place. CC: @Codyk12
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.