Search code examples
juliadraw

How to draw a circular sector in the Julia language?


I am new to the Julia language and need to draw a circular sector on an image (2-dimensional UInt8 array for gray version or 3-dimensional UInt8 array for an RGB version). Afterwards this image is to be used as a mask to select data in other arrays, so I need the result, not as an image object, but as an array of booleans or integers.

There is the way to draw a circle by means of the ImageDraw package:

draw!(img, Ellipse(CirclePointRadius(350,200,100), fill = tue))

but found no way to provide a start and end angle.


Solution

  • I think Julia is a great language, because (among other things) all libraries are implemented in the same language and you have ease acces to their sources.

    And in this way, I have been able to modify the ellipse2d.jl script of the ImageDraw library.

    The modification consits of adding another definition of the draw! funciton for ellipse objects (multiple dispatch of Julia is also great) that accepts a start and end angle.

    I think the best way could be to define new objects, ellipse_sector and circle_sector, which would be the same as the ellipse and circle objects but with two more members: start_angle and end_angle. Then the correspondent drawing functions should be implemented. I would like to write to the ImageDraw package developers in order to make this suggestion or even offer me to make these changes, but I do not know the manage of github.

    My solution, instead, does not modify any existing object, just adds a method to the draw! function that accpets two more arguments: startAngle and endAngle.

    Here is the code, to be copied to the end of the ellipse2d.jl script:

    function draw!(img::AbstractArray{T, 2}, ellipse::Ellipse, startAng::Real, endAng::Real, color::T) where T<:Colorant
        # Solution to find out if an angle lies between two given ones, borrowed from:
        # https://stackoverflow.com/questions/11406189/determine-if-angle-lies-between-2-other-angles/11412077#11412077
    
        # Make all angles to lie in [0, 2π)
        # rem2pi(ϕ, RoundNearest) returns the remainder of the division by 2π in the range [−π,π]
        # mod2pi returns the remainder of the division by 2π in the range [0,2π)
        Angle1 = mod2pi(startAng)
        Angle2 = mod2pi(endAng)
    
        # make the angle from angle1 to angle2 to be <= 180 degrees
        rAngle = mod2pi( mod2pi(Angle2 - Angle1) + 2π)
        if rAngle >= π
            Angle1, Angle2 = Angle2, Angle1 # Swaps the values
        end # if
    
        ys = Int[]
        xs = Int[]
        break_point = 0
        if ellipse.fill == false
            break_point = ((ellipse.ρy - ellipse.thickness) / ellipse.ρy) ^ 2 + ((ellipse.ρx - ellipse.thickness) / ellipse.ρx) ^ 2 
        end
        for i in ellipse.center.y - ellipse.ρy : ellipse.center.y + ellipse.ρy
            for j in ellipse.center.x - ellipse.ρx: ellipse.center.x + ellipse.ρx
                y = i - ellipse.center.y
                x = j - ellipse.center.x
                val = (x / ellipse.ρy) ^ 2 + (y / ellipse.ρx) ^ 2
                # atan(y, x) returns the angle in the correct quadrant [−π,π], not like atan(y/x)
                # But make it to be in the range [0, 2π)by means of mod2pi()
                ang = mod2pi( atan(y, x) )
    
                # Test if the angle lies betwen the startAngle and the endAngle
                if (Angle1 <= Angle2)
                    AngleIsBetween = ang >= Angle1 && ang <= Angle2
                else
                    AngleIsBetween = ang >= Angle1 || ang <= Angle2
                end # if
                if val < 1 && val >= break_point && AngleIsBetween
                    push!(ys, i)
                    push!(xs, j)
                end
            end
        end
        for (yi, xi) in zip(ys, xs)
            drawifinbounds!(img, yi, xi, color)
        end
        img
    end