Skip to content

Commit

Permalink
Redesign color constructors
Browse files Browse the repository at this point in the history
This eliminates the macro-based constructor generation.
Instead, this uses the abstract types to provide the constructor interfaces.
This also allows grays to be used in the constructor argument in more cases.
  • Loading branch information
kimikage committed Mar 6, 2021
1 parent 3507106 commit 6533522
Show file tree
Hide file tree
Showing 10 changed files with 310 additions and 270 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,4 +427,4 @@ In most cases, adding a new color space is quite straightforward:

In special cases, there may be other considerations:
- For RGB-related types, 0 means "black" and 1 means "saturated." If your type has unusual numeric interpretation, you may need to add a new number type to [FixedPointNumbers](https://github.com/JeffBezanson/FixedPointNumbers.jl) and set up appropriate `eltype_default` and `eltype_ub` traits.
- If your type has extra fields, check the "Generated code" section of `types.jl` carefully. You may need to define a `colorfields` function and/or call `@make_constructors` or `@make_alpha` manually.
- If your type has extra fields, check the "Generated code" section of `types.jl` carefully. You may need to define a `colorfields` function and/or call `@make_alpha` manually.
7 changes: 0 additions & 7 deletions src/conversions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,6 @@ convert(::Type{T}, x::Gray24) where {T<:Real} = convert(T, gray(x))
(::Type{T})(x::AbstractGray) where {T<:Real} = T(gray(x))
Base.real(x::AbstractGray) = gray(x)

# Define some constructors that just call convert since the fallback constructor in Base
# is removed in Julia 0.7
# The parametric types are handled in @make_constructors and @make_alpha
for t in (:ARGB32, :Gray24, :RGB24)
@eval $t(x) = convert($t, x)
end

# reinterpret
for T in (RGB24, ARGB32, Gray24, AGray32)
@eval begin
Expand Down
8 changes: 8 additions & 0 deletions src/traits.jl
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,14 @@ to_top(::Type{Colorant{T,N}}) where {T,N} = Colorant{T,N}

to_top(c::Colorant) = to_top(typeof(c))


# Return the number of components in the color
# Note this is different from div(sizeof(c), sizeof(eltype(c))) (e.g., XRGB)
length(::Type{<:ColorantN{N}}) where N = N

length(c::Colorant) = length(typeof(c))


# eltype(RGB{Float32}) -> Float32
eltype(::Type{C}) where {C<:Colorant{T}} where {T} = T

Expand Down
396 changes: 180 additions & 216 deletions src/types.jl

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions test/conversions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,7 @@ end

@test reinterpret(AGray32, 0xab121212) === AGray32(0.071, 0.671)

@test_throws MethodError reinterpret(UInt32, ARGB{N0f8}())
@test_throws ErrorException reinterpret(UInt32, ARGB{N0f8}())
@test_throws ErrorException reinterpret(ARGB{N0f8}, 0x12345678)
end

Expand All @@ -843,7 +843,7 @@ end
ret = @test_throws ArgumentError RGB24(0x00ffffff)
@test occursin("Use `reinterpret(RGB24, 0x00ffffff)`", ret.value.msg)
ret = @test_throws ArgumentError ARGB32(0x00ffffff)
@test_broken occursin("Use `reinterpret(ARGB32, 0x00ffffff)`", ret.value.msg)
@test occursin("Use `reinterpret(ARGB32, 0x00ffffff)`", ret.value.msg)
ret = @test_throws ArgumentError Gray24(0x00ffffff)
@test occursin("Use `reinterpret(Gray24, 0x00ffffff)`", ret.value.msg)
ret = @test_throws ArgumentError AGray32(0x00ffffff)
Expand Down
64 changes: 64 additions & 0 deletions test/dummytypes.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
module DummyTypes

using ColorTypes

export C2, C2A, C4, AC4
export AnaglyphColor, CMYK, ACMYK

struct C2{T <: Real} <: Color{T,2}
c1::T
c2::T
end

struct C2A{T} <: ColorAlpha{C2{T},T,3} # T should be <: Real
c1::T
c2::T
alpha::T
end
ColorTypes.coloralpha(::Type{<:C2}) = C2A

struct C4{T <: Real} <: Color{T,4}
c1::T
c2::T
c3::T
c4::T
end
ColorTypes.eltype_default(::Type{<:C4}) = Int16

struct AC4{T <: Real} <: AlphaColor{C4{T},T,5}
alpha::T
c1::T
c2::T
c3::T
c4::T
AC4{T}(c1::T, c2::T, c3::T, c4::T, alpha::T=oneunit(T)) where {T} = new{T}(alpha, c1, c2, c3, c4)
end
ColorTypes.alphacolor(::Type{<:C4}) = AC4
ColorTypes.eltype_default(::Type{<:AC4}) = Int16

# dummy type for testing 2-component color
struct AnaglyphColor{T} <: Color{T,2} # not `TransparentGray`
left::T
right::T
end

# dummy type for testing 4-component color
struct CMYK{T <: Fractional} <: Color{T,4} # not `Transparent3`
c::T
m::T
y::T
k::T
CMYK{T}(c::T, m::T, y::T, k::T) where {T} = new{T}(c, m, y, k)
end

# dummy type for testing 5-component color
struct ACMYK{T <: Fractional} <: AlphaColor{CMYK{T},T,5}
alpha::T
c::T
m::T
y::T
k::T
ACMYK{T}(c::T, m::T, y::T, k::T, alpha::T=oneunit(T)) where {T} = new{T}(alpha, c, m, y, k)
end

end # module
14 changes: 6 additions & 8 deletions test/operations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ using ColorTypes.FixedPointNumbers
using ColorTypes.Random
using Test

# dummy type
struct C5{T} <: Color{T,5}
c1::T; c2::T; c3::T; c4::T; c5::T
end
isdefined(Main, :DummyTypes) || include("dummytypes.jl")
using .DummyTypes

@testset "rand" begin
function all_in_range(c::C) where {C}
Expand Down Expand Up @@ -71,14 +69,14 @@ end
@test @inferred(mapc(x->2x, RGB{N0f8}(0.04,0.2,0.3))) === RGB(map(x->2*N0f8(x), (0.04,0.2,0.3))...)
@test @inferred(mapc(sqrt, RGBA{N0f8}(0.04,0.2,0.3,0.7))) === RGBA(map(x->sqrt(N0f8(x)), (0.04,0.2,0.3,0.7))...)
@test @inferred(mapc(x->1.5f0x, RGBA{N0f8}(0.04,0.2,0.3,0.4))) === RGBA(map(x->1.5f0*N0f8(x), (0.04,0.2,0.3,0.4))...)
@test @inferred(mapc(sqrt, C5{N0f8}(0.04,0.2,0.3,0.7,0.1))) === C5(map(x->sqrt(N0f8(x)), (0.04,0.2,0.3,0.7,0.1))...)
@test @inferred(mapc(sqrt, ACMYK{N0f8}(0.04,0.2,0.3,0.7,0.1))) === ACMYK(map(x->sqrt(N0f8(x)), (0.04,0.2,0.3,0.7,0.1))...)

@test @inferred(mapc(max, Gray{N0f8}(0.2), Gray{N0f8}(0.3))) === Gray{N0f8}(0.3)
@test @inferred(mapc(-, AGray{Float32}(0.3), AGray{Float32}(0.2))) === AGray{Float32}(0.3f0-0.2f0,0.0)
@test @inferred(mapc(min, RGB{N0f8}(0.2,0.8,0.7), RGB{N0f8}(0.5,0.2,0.99))) === RGB{N0f8}(0.2,0.2,0.7)
@test @inferred(mapc(+, RGBA{N0f8}(0.2,0.8,0.7,0.3), RGBA{Float32}(0.5,0.2,0.99,0.5))) === RGBA(0.5f0+N0f8(0.2),0.2f0+N0f8(0.8),0.99f0+N0f8(0.7),0.5f0+N0f8(0.3))
@test @inferred(mapc(+, HSVA(0.1,0.8,0.3,0.5), HSVA(0.5,0.5,0.5,0.3))) === HSVA(0.1+0.5,0.8+0.5,0.3+0.5,0.5+0.3)
@test @inferred(mapc(+, C5(0.1,0.8,0.3,0.5,0.2), C5(0.5,0.5,0.5,0.5,0.5))) === C5(0.1+0.5,0.8+0.5,0.3+0.5,0.5+0.5,0.2+0.5)
@test @inferred(mapc(+, ACMYK(0.1,0.8,0.3,0.5,0.2), ACMYK(0.5,0.5,0.5,0.5,0.5))) === ACMYK(0.1+0.5,0.8+0.5,0.3+0.5,0.5+0.5,0.2+0.5)

@test_throws ArgumentError mapc(min, RGB{N0f8}(0.2,0.8,0.7), BGR{N0f8}(0.5,0.2,0.99))
@test @inferred(mapc(abs, -2)) === 2
Expand All @@ -91,7 +89,7 @@ end
@test @inferred(reducec(+, 0.0, AGray(0.3, 0.8))) === 0.3 + 0.8
@test @inferred(reducec(+, 0.0, RGB(0.3, 0.8, 0.5))) === (0.3 + 0.8) + 0.5
@test @inferred(reducec(+, 0.0, RGBA(0.3, 0.8, 0.5, 0.7))) === ((0.3 + 0.8) + 0.5) + 0.7
@test @inferred(reducec(+, 0.0, C5(0.3, 0.8, 0.5, 0.7, 0.2))) === (((0.3 + 0.8) + 0.5) + 0.7) + 0.2
@test @inferred(reducec(+, 0.0, ACMYK(0.3, 0.8, 0.5, 0.7, 0.2))) === (((0.3 + 0.8) + 0.5) + 0.7) + 0.2
@test @inferred(reducec(&, true, Gray(true)))
@test !(@inferred(reducec(&, false, Gray(true))))
@test !(@inferred(reducec(&, true, Gray(false))))
Expand All @@ -110,7 +108,7 @@ end
@test @inferred(mapreducec(x->x^2, +, 0.0, AGray(0.3, 0.8))) === 0.3^2 + 0.8^2
@test @inferred(mapreducec(x->x^2, +, 0.0, RGB(0.3, 0.8, 0.5))) === (0.3^2 + 0.8^2) + 0.5^2
@test @inferred(mapreducec(x->x^2, +, 0.0, RGBA(0.3, 0.8, 0.5, 0.7))) === ((0.3^2 + 0.8^2) + 0.5^2) + 0.7^2
@test @inferred(mapreducec(x->x^2, +, 0.0, C5(0.3, 0.8, 0.5, 0.7, 0.2))) === (((0.3^2 + 0.8^2) + 0.5^2) + 0.7^2) + 0.2^2
@test @inferred(mapreducec(x->x^2, +, 0.0, ACMYK(0.3, 0.8, 0.5, 0.7, 0.2))) === (((0.3^2 + 0.8^2) + 0.5^2) + 0.7^2) + 0.2^2

@test !(@inferred(mapreducec(x->!x, &, true, Gray(true))))
@test !(@inferred(mapreducec(x->!x, &, false, Gray(true))))
Expand Down
15 changes: 2 additions & 13 deletions test/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,8 @@ using ColorTypes
using ColorTypes.FixedPointNumbers
using Test

# dummy type for testing 2-component color
struct AnaglyphColor{T} <: Color{T,2} # not `TransparentGray`
left::T; right::T
end
# dummy type for testing 4-component color
struct CMYK{T} <: Color{T,4} # not `Transparent3`
c::T; m::T; y::T; k::T
end
# dummy type for testing 5-component color
struct ACMYK{T} <: AlphaColor{CMYK{T},T,5}
alpha::T; c::T; m::T; y::T; k::T
ACMYK{T}(c, m, y, k, alpha=1) where T = new{T}(alpha, c, m, y, k)
end
isdefined(Main, :DummyTypes) || include("dummytypes.jl")
using .DummyTypes

SP = VERSION >= v"1.6.0-DEV.771" ? " " : "" # JuliaLang/julia #37085

Expand Down
16 changes: 2 additions & 14 deletions test/traits.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,8 @@ using ColorTypes.FixedPointNumbers
using Test
using ColorTypes: ColorTypeResolutionError

# dummy types
struct C2{T <: Real} <: Color{T,2}
c1::T; c2::T;
end
struct C2A{T} <: ColorAlpha{C2{T},T,3}
c1::T; c2::T; alpha::T
end
struct C4{T} <: Color{T,4}
c1::T; c2::T; c3::T; c4::T
end
struct AC4{T} <: AlphaColor{C4{T},T,5}
alpha::T; c1::T; c2::T; c3::T; c4::T
AC4{T}(c1, c2, c3, c4, alpha=1) where T = new{T}(alpha, c1, c2, c3, c4)
end
isdefined(Main, :DummyTypes) || include("dummytypes.jl")
using .DummyTypes

struct StrangeGray{Something,T <: Integer} <: AbstractGray{Normed{T}}
val::T
Expand Down
54 changes: 45 additions & 9 deletions test/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ using ColorTypes
using ColorTypes.FixedPointNumbers
using Test

isdefined(Main, :DummyTypes) || include("dummytypes.jl")
using .DummyTypes

@testset "compatibility tests for ARGB32/AGray32" begin
# cf. PR #146
Expand Down Expand Up @@ -110,6 +112,8 @@ end
@test C(val1) === C{typeof(val1)}(0.2,0.2,0.2)
@test C{N0f8}(val1) === C{N0f8}(0.2,0.2,0.2)
end
# 0-arg constructor
@test C() === C{N0f8}(0, 0, 0)

@test_throws ArgumentError C(2,1,0) # integers

Expand Down Expand Up @@ -141,8 +145,8 @@ end
@test C{N0f16}(val1,val2,val1,0.8) === C(0.2N0f16,0.6N0f16,0.2N0f16,0.8N0f16)
end
# 1-arg constructor
@test_broken C(val1) === C{typeof(val1)}(0.2,0.2,0.2,1)
@test_broken C{N0f8}(val1) === C{N0f8}(0.2,0.2,0.2,1)
@test C(val1) === C{typeof(val1)}(0.2,0.2,0.2,1)
@test C{N0f8}(val1) === C{N0f8}(0.2,0.2,0.2,1)
end

@test_throws ArgumentError C(2,1,0) # integers
Expand Down Expand Up @@ -175,13 +179,16 @@ end
@test RGB24(val1) === RGB24(0.2,0.2,0.2)
@test ARGB32(val1) === ARGB32(0.2,0.2,0.2,1)
end
# 0-arg constructor
@test RGB24() === RGB24(0, 0, 0)
@test ARGB32() === ARGB32(0, 0, 0, 1)

@test_throws ArgumentError RGB24(2,1,0) # integers
@test_throws ArgumentError ARGB32(2,1,0) # integers
@test_throws ArgumentError ARGB32(2,1,0,1) # integers

# https://github.com/JuliaGraphics/ColorTypes.jl/pull/183#issuecomment-616958191
@test_broken ARGB32(-0.00196, 0.0, 1.00196) === ARGB32(0, 0, 1)
@test ARGB32(-0.00196, 0.0, 1.00196) === ARGB32(0, 0, 1)

for val in (1.2, 1.2f0, N4f12(1.2), -0.2)
@test_throws ArgumentError RGB24(val,val,val)
Expand Down Expand Up @@ -212,7 +219,8 @@ end
@test RGB24(Gray(0.2), 0.3, 0.4) === RGB24(0.2, 0.3, 0.4)
@test ARGB32(0.2, 0.3, 0.4, Gray(0.5)) === ARGB32(0.2, 0.3, 0.4, 0.5)
@test RGB(0.2, Gray24(0.3), 0.4) === RGB(0.2, 0.3N0f8, 0.4)
@test_throws MethodError HSV(0.2, 0.3, Gray(0.4))
@test HSV(0.2, 0.3, Gray(0.4)) === HSV(0.2, 0.3, 0.4)
@test ALab(0.2, 0.3, 0.4, Gray24(0.5)) === ALab(0.2, 0.3, 0.4, 0.5N0f8)
end

@testset "gray constructors" begin
Expand All @@ -238,6 +246,8 @@ end
@test_throws ArgumentError AGray32(val, 0.8)
end
@test Gray() === Gray{N0f8}(0)
@test Gray24() === Gray24(0)
@test AGray32() === AGray32(0, 1)
@test Gray(Gray()) === Gray() # no StackOverflowError
@test Gray(1) === Gray{N0f8}(1)
@test Gray(true) === Gray{Bool}(1)
Expand Down Expand Up @@ -272,16 +282,42 @@ end
@test C(1,0,0,0.8) === C{Float64}(1,0,0,0.8)
@test C(1,0,0) === C{et}(1,0,0,1)
@test C(1,0,0,1) === C{et}(1,0,0,1)
if C <: TransparentRGB
@test C(1N0f8, 0.6N0f8, 0N0f8) === C{et}(1, 0.6, 0, 1)
else
@test_broken C(1N0f8, 0.6N0f8, 0N0f8) === C{et}(1, 0.6, 0, 1) # issue #156
end
@test C(1N0f8, 0.6N0f8, 0N0f8) === C{et}(1, 0.6, 0, 1) # issue #156
@test C() === C{et}(0,0,0,1)
@test C(C()) === C() # no StackOverflowError
end
end

@testset "constructors for minor types" begin
@test C2() === C2{Float32}(0, 0)
@test_throws MethodError C2(1)
@test C2(0.2, 0) === C2{Float64}(0.2, 0.0)
# The following is the result of the default constructor having priority.
# If you give preference to `eltype_default`, define the constructor
# explicitly to prevent implicit argument conversion.
@test C2(0, 1) === C2{Int}(0, 1) # !== C2{Float32}(0, 1)

@test C2A() === C2A{Float32}(0, 0, 1)
@test C2A(1) === C2A{Int}(1, 1)
@test C2A(0.2, 0) === C2A{Float64}(0.2, 0.0, 1.0)
@test C2A(0, 2, 1) === C2A{}(0, 2, 1)

@test C4() === C4{Int16}(0, 0, 0, 0)
@test_throws MethodError C4(1)
@test_throws MethodError C4(1, 2)
@test_throws MethodError C4(1, 2, 3)
@test C4(0.2, 0.5f0, 0.4, 0) === C4{Float64}(0.2, 0.5, 0.4, 0.0)
@test C4(0, true, 0x2, 3) === C4{Int}(0, 1, 2, 3)
@test C4(false, true, 0x2, Int8(3)) === C4{Int16}(0, 1, 2, 3)

@test AC4() === AC4{Int16}(0, 0, 0, 0, 1)
@test_throws MethodError AC4(1)
@test_throws MethodError AC4(1, 2)
@test_throws MethodError AC4(1, 2, 3)
@test AC4(0.2, 0.5f0, 0.4, 0) === AC4{Float64}(0.2, 0.5, 0.4, 0.0, 1.0)
@test AC4(false, true, 0x2, Int8(3), Int16(1)) === AC4{Int16}(0, 1, 2, 3, 1)
end

@testset "coloralpha for types" begin
@test @inferred(coloralpha(RGB)) === RGBA
@test @inferred(coloralpha(RGBA)) === RGBA
Expand Down

0 comments on commit 6533522

Please sign in to comment.