diff --git a/src/algorithms.jl b/src/algorithms.jl index dda69b4c..aced0a85 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -180,10 +180,11 @@ may also be specified. value. This behavior might change in a future release. """ -function MSC(h) +MSC(h) = MSC(Float64(h)) +function MSC(h::Float64) #Wrap h to [0, 360] range - h = normalize_hue(Float64(h)) + h = normalize_hue(h) #Selecting edge of RGB cube; R=1 G=2 B=3 # p #variable @@ -218,7 +219,7 @@ function MSC(h) col = ntuple(i -> i == p ? cpc : Float64(i == t), Val(3)) - return convert(LCHuv, RGB(col...)) + return convert(LCHuv{Float64}, RGB{Float64}(col...)) end @@ -233,37 +234,38 @@ function MSC(h, l; linear::Bool=false) pend_l = l > pmid.l ? 100.0 : 0.0 return (pend_l - l) / (pend_l - pmid.l) * pmid.c end - return find_maximum_chroma(LCHuv{Float64}(l, 0, h)) + return find_maximum_chroma(LCHuv{Float64}(l, 0.0, h)) end # This function finds the maximum chroma for the lightness `c.l` and hue `c.h` # by means of the binary search. Even though this requires more than 20 # iterations, somehow, this is fast. -function find_maximum_chroma(c::C, - low::Real=0, - high::Real=180) where {T, C<:Union{LCHab{T}, LCHuv{T}}} +function find_maximum_chroma(c::C) where {T, C<:LCHuv{T}} + _find_maximum_chroma(c, convert(T, 0), convert(T, 180)) +end +function _find_maximum_chroma(c::C, low::T, high::T) where {T, C<:Union{LCHab{T}, LCHuv{T}}} err = convert(T, 1e-6) - l, h = convert(T, low), convert(T, high) - h - l < err && return l - - mid = convert(T, (l + h) / 2) - min(mid - l, h - mid) == zero(T) && return l - lchm = C(c.l, mid, c.h) - rgbm = xyz_to_linear_rgb(convert(XYZ{T}, lchm)) - clamped = max(red(rgbm), green(rgbm), blue(rgbm)) > 1-err || - min(red(rgbm), green(rgbm), blue(rgbm)) <= 0 - if clamped - return find_maximum_chroma(c, l, mid)::T - else - return find_maximum_chroma(c, mid, h)::T + l, h = low, high + while true + mid = convert(T, (l + h) * oftype(l, 0.5)) + @fastmath min(mid - l, h - mid) < err && break + lchm = C(c.l, mid, c.h) + rgbm = xyz_to_linear_rgb(convert(XYZ{T}, lchm)) + clamped = max(red(rgbm), green(rgbm), blue(rgbm)) > 1-err || + min(red(rgbm), green(rgbm), blue(rgbm)) <= 0 + l = clamped ? l : mid + h = clamped ? mid : h end + return l end const LAB_HUE_Y = 102.85123437653252 # hue(convert(Lab, RGB(1.0, 1.0, 0.0))) -function find_maximum_chroma(c::LCHab{T}) where T - maxc = find_maximum_chroma(c, 0, 135) - +function find_maximum_chroma(c::C) where {T, C<:LCHab{T}} + find_maximum_chroma(c, convert(T, 0), convert(T, 135)) +end +function find_maximum_chroma(c::C, low::T, high::T) where {T, C<:LCHab{T}} + maxc = _find_maximum_chroma(c, low, high) # The sRGB gamut in LCHab space has a *hollow* around the yellow corner. # Since the following boundary is based on the D65 white point, the values # should be modified on other conditions. diff --git a/src/colormaps.jl b/src/colormaps.jl index 5b8da823..232482e8 100644 --- a/src/colormaps.jl +++ b/src/colormaps.jl @@ -122,7 +122,8 @@ distinguishable_colors(n::Integer; kwargs...) = distinguishable_colors(n, Vector """ sequential_palette(h, N::Int=100; ) -Implements the color palette creation technique by [Wijffelaars, M., et al. (2008)](http://magnaview.nl/documents/MagnaView-M_Wijffelaars-Generating_color_palettes_using_intuitive_parameters.pdf). +Implements the color palette creation technique by +[Wijffelaars, M., et al. (2008)](https://dl.acm.org/doi/10.1111/j.1467-8659.2008.01203.x). Colormaps are formed using Bézier curves in LCHuv colorspace with some constant hue. In addition, start and end points can be given @@ -130,138 +131,144 @@ that are then blended to the original hue smoothly. # Arguments -- N - number of colors -- h - the main hue [0,360] -- c - the overall lightness contrast [0,1] -- s - saturation [0,1] -- b - brightness [0,1] -- w - cold/warm parameter, i.e. the strength of the starting color [0,1] -- d - depth of the ending color [0,1] -- wcolor - starting color (warmness) -- dcolor - ending color (depth) -- logscale - true/false for toggling logspacing +- `N` - number of colors +- `h` - the main hue [0,360] +- `c` - the overall lightness contrast [0,1] +- `s` - saturation [0,1] +- `b` - brightness [0,1] +- `w` - cold/warm parameter, i.e. the strength of the starting color [0,1] +- `d` - depth of the ending color [0,1] +- `wcolor` - starting color (warmness) +- `dcolor` - ending color (depth) +- `logscale` - `true`/`false` for toggling logspacing """ function sequential_palette(h, N::Int=100; + w=0.15, + d=0.0, c=0.88, s=0.6, b=0.75, - w=0.15, - d=0.0, - wcolor=RGB(1,1,0), - dcolor=RGB(0,0,1), - logscale=false) + wcolor=RGB(1.0, 1.0, 0.0), + dcolor=RGB(0.0, 0.0, 1.0), + logscale::Bool=false) + F = Float64 + return _sequential_palette(N, logscale, F(h), F(w), F(d), F(c), F(s), F(b), + RGB{F}(wcolor), RGB{F}(dcolor)) +end - function mix_hue(a, h0, h1) - m = normalize_hue(180.0 + h1 - h0) - 180.0 - normalize_hue(h0 + a * m) - end - function mix_linearly(a::C, b::C, s) where C<:Color - base_color_type(C)((1-s)*comp1(a)+s*comp1(b), (1-s)*comp2(a)+s*comp2(b), (1-s)*comp3(a)+s*comp3(b)) - end +mix_hue(h0::Float64, h1::Float64, w) = h0 + w * (normalize_hue(180.0 + h1 - h0) - 180.0) +mix_linearly(a::Float64, b::Float64, w) = (1.0 - w) * a + w * b +function mix_linearly(a::LCHuv{Float64}, b::LCHuv{Float64}, w) + l = mix_linearly(a.l, b.l, w) + c = mix_linearly(a.c, b.c, w) + h = mix_hue(a.h, b.h, w) + LCHuv{Float64}(l, c, h) # without hue normalization +end - pstart=convert(LCHuv, wcolor) - p1=MSC(h) - #p0=LCHuv(0,0,h) #original end point - pend=convert(LCHuv, dcolor) +function _sequential_palette(N, logscale, h, w, d, c, s, b, wcolor, dcolor) + function term_point(pb, l1, l2, weight) + pl = mix_linearly(l1, l2, weight) + ph = mix_hue(h, pb.h, weight) + mc = find_maximum_chroma(LCHuv{Float64}(pl, 0.0, ph)) + pc = min(mc, weight * s * pb.c) + LCHuv{Float64}(pl, pc, ph) + end - #multi-hue start point - p2l = 100 * (1. - w) + w * pstart.l - p2h=mix_hue(w,h,pstart.h) - p2c=min(MSC(p2h,p2l), w*s*pstart.c) - p2=LCHuv(p2l,p2c,p2h) + pstart0 = convert(LCHuv{Float64}, wcolor) + pend0 = convert(LCHuv{Float64}, dcolor) + # Modify the hue of gray + pstart = LCHuv{Float64}(pstart0.l, pstart0.c, pstart0.c < 1e-4 ? h : pstart0.h) + pend = LCHuv{Float64}(pend0.l, pend0.c, pend0.c < 1e-4 ? h : pend0.h) - #multi-hue ending point - p0l=20.0*d - p0h=mix_hue(d,h,pend.h) - p0c=min(MSC(p0h,p0l), d*s*pend.c) - p0=LCHuv(p0l,p0c,p0h) + p2 = term_point(pstart, 100.0, pstart.l, w) # multi-hue start point + p1 = MSC(h) + p0 = term_point(pend, 0.0, 20.0, d) # multi-hue ending point - q0=mix_linearly(p0,p1,s) - q2=mix_linearly(p2,p1,s) - q1=mix_linearly(q0,q2,0.5) + q0 = mix_linearly(p0, p1, s) + q2 = mix_linearly(p2, p1, s) + q1 = mix_linearly(q0, q2, 0.5) - pal = RGB{Float64}[] + pal = Vector{RGB{Float64}}(undef, N) - if logscale - absc = exp10.(range(-2., stop=0., length=N)) - else - absc = range(0.,stop=1.,length=N) - end - - for t in absc - u=1.0-t + step = 1.0 / (N - 1.0) + for i = 0:N-1 + if logscale + u = 1.0 - exp10(2.0 * i * step - 2.0) + else + u = 1.0 - i * step + end - #Change grid to favor light colors and to be uniform along the curve - u = (125.0-125.0*0.2^((1.0-c)*b+u*c)) - u = invBezier(u,p0.l,p2.l,q0.l,q1.l,q2.l) + # Change grid to favor light colors and to be uniform along the curve + lt = 125.0 - 125.0 * 0.2^mix_linearly(b, u, c) + tt = inv_bezier(lt, p0.l, p2.l, q0.l, q1.l, q2.l) - #Get color components from Bezier curves - ll = Bezier(u, p0.l, p2.l, q0.l, q1.l, q2.l) - cc = Bezier(u, p0.c, p2.c, q0.c, q1.c, q2.c) - hh = Bezier(u, p0.h, p2.h, q0.h, q1.h, q2.h) + # Get color components from Bezier curves + ll = bezier(tt, p0.l, p2.l, q0.l, q1.l, q2.l) + cc = bezier(tt, p0.c, p2.c, q0.c, q1.c, q2.c) + hh = bezier(tt, p0.h, p2.h, q0.h, q1.h, q2.h) - push!(pal, convert(RGB, LCHuv(ll,cc,hh))) + @inbounds pal[i + 1] = convert(RGB{Float64}, LCHuv{Float64}(ll, cc, hh)) end - pal end """ diverging_palette(h1, h2, N::Int=100; ) -Create diverging palettes by combining 2 sequential palettes +Create diverging palettes by combining 2 sequential palettes. # Arguments -- N - number of colors -- h1 - the main hue of the left side [0,360] -- h2 - the main hue of the right side [0,360] -- c - the overall lightness contrast [0,1] -- s - saturation [0,1] -- b - brightness [0,1] -- w - cold/warm parameter, i.e. the strength of the starting color [0,1] -- d1 - depth of the ending color in the left side [0,1] -- d2 - depth of the ending color in the right side [0,1] -- wcolor - starting color (warmness) -- dcolor1 - ending color of the left side (depth) -- dcolor2 - ending color of the right side (depth) -- logscale - true/false for toggling logspacing +- `N` - number of colors +- `mid` - the position of the midpoint (0,1) +- `h1` - the main hue of the first part [0,360] +- `h2` - the main hue of the latter part [0,360] +- `c` - the overall lightness contrast [0,1] +- `s` - saturation [0,1] +- `b` - brightness [0,1] +- `w` - cold/warm parameter, i.e. the strength of the starting color [0,1] +- `d1` - depth of the ending color in the first part [0,1] +- `d2` - depth of the ending color in the latter part [0,1] +- `wcolor` - starting color, i.e. the middle color (warmness) +- `dcolor1` - ending color of the first part (depth) +- `dcolor2` - ending color of the latter part (depth) +- `logscale` - `true`/`false` for toggling logspacing """ function diverging_palette(h1, h2, N::Int=100; mid=0.5, - c=0.88, - s=0.6, - b=0.75, w=0.15, d1=0.0, d2=0.0, - wcolor=RGB(1,1,0), - dcolor1=RGB(1,0,0), - dcolor2=RGB(0,0,1), - logscale=false) - - if isodd(N) - n=N-1 - else - n=N - end - N1 = max(ceil(Int, mid*n), 1) - N2 = Int(max(n-N1, 1)) - - pal1 = sequential_palette(h1, N1+1, w=w, d=d1, c=c, s=s, b=b, wcolor=wcolor, dcolor=dcolor1, logscale=logscale) - pal1 = reverse(pal1; dims=1) - - pal2 = sequential_palette(h2, N2+1, w=w, d=d2, c=c, s=s, b=b, wcolor=wcolor, dcolor=dcolor2, logscale=logscale) - - if isodd(N) - midcol = weighted_color_mean(0.5, pal1[end], pal2[1]) - return [pal1[1:end-1]; midcol; pal2[2:end]] - else - return [pal1[1:end-1]; pal2[2:end]] + c=0.88, + s=0.6, + b=0.75, + wcolor =RGB(1.0, 1.0, 0.0), + dcolor1=RGB(1.0, 0.0, 0.0), + dcolor2=RGB(0.0, 0.0, 1.0), + logscale::Bool=false) + F = Float64 + return _diverging_palette(N, F(mid), logscale, + F(h1), F(h2), F(w), F(d1), F(d2), F(c), F(s), F(b), + RGB{F}(wcolor), RGB{F}(dcolor1), RGB{F}(dcolor2)) +end +function _diverging_palette(N, mid, logscale, h1, h2, w, d1, d2, c, s, b, wcolor, dcolor1, dcolor2) + n = isodd(N) ? (N - 1) : N + N1 = max(ceil(Int, mid * n), 1) + N2 = max(n - N1, 1) + + pal1 = _sequential_palette(N1 + 1, logscale, h1, w, d1, c, s, b, wcolor, dcolor1) + pal2 = _sequential_palette(N2 + 1, logscale, h2, w, d2, c, s, b, wcolor, dcolor2) + + pal = Vector{RGB{Float64}}(undef, N1 + N2 + isodd(N)) + # concatenation without `vcat`, which is not precompilable + @inbounds pal2[1] = weighted_color_mean(0.5, pal1[1], pal2[1]) + @inbounds for i in eachindex(pal) + pal[i] = i <= N1 ? pal1[end - i + 1] : pal2[i - N1 + iseven(N)] end + pal end @@ -270,21 +277,21 @@ end # Main function to handle different predefined colormaps # """ - colormap(cname, N=100; mid=0.5, logscale=false, kvs...]) + colormap(cname, N=100; logscale=false, ) Returns a predefined sequential or diverging colormap computed using the algorithm by Wijffelaars, M., et al. (2008). Sequential colormaps `cname` choices are: -- `Blues` -- `Greens` -- `Grays` -- `Oranges` -- `Purples`, -- `Reds` +- `"Blues"` +- `"Grays"` +- `"Greens"` +- `"Oranges"` +- `"Purples"` +- `"Reds"` -Diverging colormap choices are `RdBu`. +Diverging colormap choices are `"RdBu"`. Optionally, you can specify the number of colors `N` (default 100). @@ -294,49 +301,37 @@ Extra control is provided by keyword arguments. You can also use keyword argument names that match the argument names in [`sequential_palette`](@ref) or [`diverging_palette`](@ref). """ -function colormap(cname::String, N::Int=100; mid=0.5, logscale::Bool=false, kvs...) - - cname = lowercase(cname) - if haskey(colormaps_sequential, cname) - allowedkeys = [:h, :w, :d, :c, :s, :b, :wcolor, :dcolor] - p = copy(colormaps_sequential[cname][1:8]) - - for (k,v) in kvs - ind = findfirst(e->e==k, allowedkeys) - ind == nothing && throw(ArgumentError("Unknown keyword argument $k")) - if ind > 0 - p[ind] = v - end - end +function colormap(cname::AbstractString, N::Integer=100; kvs...) + _colormap(String(cname), Int(N), kvs) +end - # To avoid invalidation risk, it's best to make the call in a manner that inference knows the types - return sequential_palette(f64(p[1]), N, w=f64(p[2]), d=f64(p[3]), c=f64(p[4]), - s=f64(p[5]), b=f64(p[6]), wcolor=rgb8(p[7]), - dcolor=rgb8(p[8]), logscale=logscale) - - elseif haskey(colormaps_diverging, cname) - allowedkeys = [:h1, :h2, :w, :d1, :d2, :c, :s, :b, :wcolor, :dcolor1, :dcolor2] - p = copy(colormaps_diverging[cname][1:11]) - - for (k,v) in kvs - ind = findfirst(e->e==k, allowedkeys) - ind == nothing && throw(ArgumentError("Unknown keyword argument $k")) - if ind > 0 - p[ind] = v - end +function _colormap(cname::String, N::Int, kvs) + logscale = get(kvs, :logscale, false)::Bool + lcname = lowercase(cname) + F = Float64 + if haskey(colormaps_sequential, lcname) + keys_s = (:h, :w, :d, :c, :s, :b, :wcolor, :dcolor) + Ts = Tuple{F, F, F, F, F, F, RGB{F}, RGB{F}} + pbs = colormaps_sequential[lcname]::Ts + for k in keys(kvs) + k === :logscale && continue + k in keys_s || throw(ArgumentError("Unknown keyword argument: $k")) end - - return diverging_palette(f64(p[1]), f64(p[2]), N, w=f64(p[3]), d1=f64(p[4]), d2=f64(p[5]), - c=f64(p[6]), s=f64(p[7]), b=f64(p[8]), wcolor=rgb8(p[9]), - dcolor1=rgb8(p[10]), dcolor2=rgb8(p[11]), mid=mid, - logscale=logscale) - - else - throw(ArgumentError(string("Unknown colormap: ", cname))) + ps(i) = oftype(pbs[i], get(kvs, keys_s[i], pbs[i])) + return _sequential_palette(N, logscale, (ps(i) for i in eachindex(pbs))...) end + if haskey(colormaps_diverging, lcname) + keys_d = (:h1, :h2, :w, :d1, :d2, :c, :s, :b, :wcolor, :dcolor1, :dcolor2) + Td = Tuple{F, F, F, F, F, F, F, F, RGB{F}, RGB{F}, RGB{F}} + pbd = colormaps_diverging[lcname]::Td + for k in keys(kvs) + k === :logscale && continue + k === :mid && continue + k in keys_d || throw(ArgumentError("Unknown keyword argument: $k")) + end + mid = Float64(get(kvs, :mid, 0.5)) + pd(i) = oftype(pbd[i], get(kvs, keys_d[i], pbd[i])) + return _diverging_palette(N, mid, logscale, (pd(i) for i in eachindex(pbd))...) + end + throw(ArgumentError(string("Unknown colormap: ", cname))) end - -colormap(cname::AbstractString, args...; kwargs...) = colormap(String(cname), args...; kwargs...) - -f64(x) = Float64(x)::Float64 -rgb8(c) = RGB{N0f8}(c)::RGB{N0f8} diff --git a/src/conversions.jl b/src/conversions.jl index 4fc54e9e..e1764c27 100644 --- a/src/conversions.jl +++ b/src/conversions.jl @@ -59,9 +59,11 @@ convert(::Type{XYZ{T}}, c, wp::XYZ) where {T} = cnvt(XYZ{T}, c, wp) convert(::Type{Lab{T}}, c, wp::XYZ) where {T} = cnvt(Lab{T}, c, wp) convert(::Type{Luv{T}}, c, wp::XYZ) where {T} = cnvt(Luv{T}, c, wp) -# FIXME: inference helpers for LCH --> RGB conversions +# FIXME: inference helpers for LCH <--> RGB conversions convert(::Type{RGB}, c::Union{LCHab{T}, LCHuv{T}}) where {T} = cnvt(RGB{T}, cnvt(XYZ{T}, c)) convert(::Type{RGB{T}}, c::Union{LCHab{T}, LCHuv{T}}) where {T} = cnvt(RGB{T}, cnvt(XYZ{T}, c)) +convert(::Type{Lab{T}}, c::RGB{T}) where {T} = cnvt(Lab{T}, cnvt(XYZ{T}, c)) +convert(::Type{Luv{T}}, c::RGB{T}) where {T} = cnvt(Luv{T}, cnvt(XYZ{T}, c)) # Fallback to catch undefined operations cnvt(::Type{C}, c::TransparentColor) where {C<:Color} = cnvt(C, color(c)) diff --git a/src/maps_data.jl b/src/maps_data.jl index 058ac356..f87547dc 100644 --- a/src/maps_data.jl +++ b/src/maps_data.jl @@ -1,17 +1,19 @@ # Colormap parameters -const colormaps_sequential = Dict( +# To avoid over-specialization, we use `Any` as the value type, +# but the actual type should be exactly as used here. +const colormaps_sequential = Dict{String, Any}( #single hue - #name hue w d c s b wcolor dcolor - "blues" => [255, 0.3, 0.25, 0.88, 0.6, 0.75, RGB(1,1,0), RGB(0,0,1)], - "greens" => [120, 0.15, 0.18, 0.88, 0.55, 0.9, RGB(1,1,0), RGB(0,0,1)], - "grays" => [0, 0.0, 0.0, 1.0, 0.0, 0.75, RGB(1,1,0), RGB(0,0,1)], - "oranges" => [20, 0.5, 0.4, 0.83, 0.95, 0.85, RGB(1,1,0), RGB(1,0,0)], - "purples" => [265, 0.15, 0.2, 0.88, 0.5, 0.7, RGB(1,0,1), RGB(1,0,0)], - "reds" => [12, 0.15, 0.25, 0.8, 0.85, 0.6, RGB(1,1,0), RGB(0.3,0.1,0.1)] + #name hue w d c s b wcolor dcolor + "blues" => (255.0, 0.3, 0.25, 0.88, 0.6, 0.75, RGB(1.0, 1.0, 0.0), RGB(0.0, 0.0, 1.0)), + "greens" => (120.0, 0.15, 0.18, 0.88, 0.55, 0.9, RGB(1.0, 1.0, 0.0), RGB(0.0, 0.0, 1.0)), + "grays" => ( 0.0, 0.0, 0.0, 1.0, 0.0, 0.75, RGB(1.0, 1.0, 0.0), RGB(0.0, 0.0, 1.0)), + "oranges" => ( 20.0, 0.5, 0.4, 0.83, 0.95, 0.85, RGB(1.0, 1.0, 0.0), RGB(1.0, 0.0, 0.0)), + "purples" => (265.0, 0.15, 0.2, 0.88, 0.5, 0.7, RGB(1.0, 0.0, 1.0), RGB(1.0, 0.0, 0.0)), + "reds" => ( 12.0, 0.15, 0.25, 0.8, 0.85, 0.6, RGB(1.0, 1.0, 0.0), RGB(0.3, 0.1, 0.1)), ) -const colormaps_diverging = Dict( - #name h1 h2 w d1 d2 c s b wcolor dcolor dcolor2 - "rdbu" => [12, 255, 0.2, 0.6, 0.0, 0.85, 0.6, 0.65, RGB(1,1,0), RGB(1,0,0), RGB(0,0,1)] +const colormaps_diverging = Dict{String, Any}( + #name h1 h2 w d1 d2 c s b wcolor dcolor1 dcolor2 + "rdbu" => ( 12.0, 255.0, 0.2, 0.6, 0.0, 0.85, 0.6, 0.65, RGB(1.0, 1.0, 0.0), RGB(1.0, 0.0, 0.0), RGB(0.0, 0.0, 1.0)), ) diff --git a/src/precompile.jl b/src/precompile.jl index b2c54aa4..61155a88 100644 --- a/src/precompile.jl +++ b/src/precompile.jl @@ -29,5 +29,6 @@ function _precompile_() precompile(Tuple{typeof(colordiff),RGB{T},RGB{T}}) end precompile(Tuple{typeof(colormap),String}) + precompile(Tuple{typeof(colormap),String,Int}) precompile(Tuple{typeof(distinguishable_colors),Int}) end diff --git a/src/utilities.jl b/src/utilities.jl index 7e838449..197278d5 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -556,8 +556,8 @@ import Base: linspace Base.@deprecate linspace(start::Colorant, stop::Colorant, n::Integer=100) range(start, stop=stop, length=n) end -#Double quadratic Bezier curve -function Bezier(t::T, p0::T, p2::T, q0::T, q1::T, q2::T) where T<:Real +# Double quadratic Bezier curve +function bezier(t::T, p0::T, p2::T, q0::T, q1::T, q2::T) where T<:Real B(t,a,b,c)=a*(1.0-t)^2 + 2.0b*(1.0-t)*t + c*t^2 if t <= 0.5 return B(2.0t, p0, q0, q1) @@ -566,8 +566,8 @@ function Bezier(t::T, p0::T, p2::T, q0::T, q1::T, q2::T) where T<:Real end end -#Inverse double quadratic Bezier curve -function invBezier(t::T, p0::T, p2::T, q0::T, q1::T, q2::T) where T<:Real +# Inverse double quadratic Bezier curve +function inv_bezier(t::T, p0::T, p2::T, q0::T, q1::T, q2::T) where T<:Real invB(t,a,b,c)=(a-b+sqrt(b^2-a*c+(a-2.0b+c)*t))/(a-2.0b+c) if t < q1 return 0.5*invB(t,p0,q0,q1) diff --git a/test/colormaps.jl b/test/colormaps.jl index 5819ce31..2635ff49 100644 --- a/test/colormaps.jl +++ b/test/colormaps.jl @@ -32,7 +32,7 @@ using Test, Colors # not return values to check here, just checking that keywords can be used # Sequential default_blues = colormap("Blues", 10) - @test colormap("Blues", 10; mid=0.9) == default_blues # `mid` is for diverging colormaps only (issue #300) + @test_throws ArgumentError colormap("Blues", 10; mid=0.9) # `mid` is for diverging colormaps only (issue #300) @test colormap("Blues", 10; logscale=true) != default_blues @test colormap("Blues", 10; h=0.5) != default_blues @test colormap("Blues", 10; w=0.5) != default_blues @@ -65,16 +65,16 @@ using Test, Colors # The outputs of `colormap()` were slightly affected by the bug fix of # `MSC(h)` (issue #349). # The following were generated by `colormap()` in Colors.jl v0.9.6. - blues_old = ["F4FDFF", "B3E3F4", "65B9E7", "2978BE", "0B2857"] - greens_old = ["FAFFF7", "B6EEA0", "75C769", "308B40", "00391A"] - grays_old = ["FFFFFF", "DCDCDC", "A9A9A9", "626262", "000000"] - oranges_old = ["FFFBF6", "FFD6B4", "FF9D5F", "E2500D", "732108"] - purples_old = ["FBFBFB", "DBD7F6", "AFA7E6", "7666BF", "3C0468"] - reds_old = ["FFF1EE", "FFC4B9", "FF8576", "E72823", "6D0B0C"] - rdbu_old = ["610102", "FF8D7B", "F9F8F9", "76B4E8", "092C58"] + blues_old = (0xF4FDFF, 0xB3E3F4, 0x65B9E7, 0x2978BE, 0x0B2857) + greens_old = (0xFAFFF7, 0xB6EEA0, 0x75C769, 0x308B40, 0x00391A) + grays_old = (0xFFFFFF, 0xDCDCDC, 0xA9A9A9, 0x626262, 0x000000) + oranges_old = (0xFFFBF6, 0xFFD6B4, 0xFF9D5F, 0xE2500D, 0x732108) + purples_old = (0xFBFBFB, 0xDBD7F6, 0xAFA7E6, 0x7666BF, 0x3C0468) + reds_old = (0xFFF1EE, 0xFFC4B9, 0xFF8576, 0xE72823, 0x6D0B0C) + rdbu_old = (0x610102, 0xFF8D7B, 0xF9F8F9, 0x76B4E8, 0x092C58) - to_rgb(s) = parse(RGB, "#"*s) - max_colordiff(a1, a2) = reduce(max, colordiff.(a1, a2)) + to_rgb(s) = reinterpret(RGB24, s) + max_colordiff(a1, a2) = maximum(colordiff.(a1, a2)) @test max_colordiff(colormap("Blues", 5), to_rgb.(blues_old)) < 1 @test max_colordiff(colormap("Greens", 5), to_rgb.(greens_old)) < 1 @test max_colordiff(colormap("Grays", 5), to_rgb.(grays_old)) < 1 @@ -83,5 +83,9 @@ using Test, Colors @test max_colordiff(colormap("Reds", 5), to_rgb.(reds_old)) < 1 @test max_colordiff(colormap("RdBu", 5), to_rgb.(rdbu_old)) < 1 - # TODO: add more tests + cyans = sequential_palette(192, w=1.0, d=1.0, wcolor=RGB(1,1,1), dcolor=RGB(0,0,0)) + @test all(c -> isapprox(green(c), blue(c), atol=1e-3), cyans) + + ryr = diverging_palette(0.0, 359.999, 45, w=0.9, c=0.7, dcolor1=RGB(0,0,1)) + @test all(c -> isapprox(c[1], c[2], atol=1e-3), zip(ryr, reverse(ryr))) end