Problem with Type of Vector of Colors/Colorants

Hi all,

I’m trying to write a function that accepts an 1d-array/vector of Colors. I want to use the most abstract type Colorant from ColorTypes.jl; i.e. like this:

using Colors
using ColorTypes

function test(colors::Vector{Colorant})
    # do stuff
end

However, when I try to specify a list of concrete colors, I get an error:

c = [colorant"red", colorant"blue"]
typeof(c) # Array{RGB{FixedPointNumbers.Normed{UInt8,8}},1}

test(c) # >MethodError: no method matching test(::Array{RGB{FixedPointNumbers.Normed{UInt8,8}},1})

I find this in particular weird because the individual elements (colors) of the vector are indeed of abstract type Colorant:

typeof(c[1])<:Colorant # true
typeof(c[2])<:Colorant # true

What am I doing wrong?

I’m on Julia 1.3.1, ColorTypes v0.9.1 and Colors v0.11.2.

edit: As an additional remark, replacing the Colorant type in function test with type Any still throws the same error (?!).

Use Vector{<:Colorant} or (even more general) AbstractVector{<:Colorant}.

1 Like

Ahh okay, so the problem is that I ask for the abstract type Colorant itself and not for potential subtypes?

This also seems to work:

function test(colors::Vector{T}) where T<:Colorant
    # do stuff
end

These are probably all kind of equivalent solutions?

colors::Vector{Colorant} means that each element of colors may be a different subtype of Colorant. Internally, therefore, it is an array of pointers to “boxes” (values with a type tag), a different box for each element. (Operations on such an array are consequently rather slow because the compiled code needs to look up the type of each element at runtime.) Vector{Colorant} is one specific concrete type, and no other type is allowed.

In contrast a parameter colors::AbstractVector{<:Colorant} is declaring that the argument can be any one of a set of types V{T}, for any V <: AbstractVector and T <: Colorant. A Vector{Colorant} is allowed, but so is a Vector{RGB{Float64}}, for example. For any particular type of colors that is passed, the compiler will generate a different specialized implementation (one for each type). For example, a colors::Vector{RGB{Float64}} is stored as an array of “inlined” (r,g,b) triplets (3 Float64 values in a row for each color) with no pointers or type tags for individual elements, and since the Colorant type is known at compile time the generated code can be quite efficient (as efficient as operating in C on an array of struct { double r; double g; double b; } values).

See also the discussion here, for example, and the Julia manual on Parametric Types.

2 Likes

Thanks a lot for the explanation and links! This is really helpful (and important to understand). I’ll read this part of the manual more thoroughly this time :upside_down_face: