From 5c678f267d3a03cf4f9859879b798c1fb854f227 Mon Sep 17 00:00:00 2001 From: Evert Provoost Date: Sun, 19 May 2024 09:07:37 +0200 Subject: [PATCH] Add Oklab and Oklch (#533) --- Project.toml | 2 +- docs/crosssectionalcharts.jl | 6 +++ docs/src/constructionandconversion.md | 7 ++++ src/conversions.jl | 57 ++++++++++++++++++++++++--- src/precompile.jl | 4 +- src/utilities.jl | 19 ++++----- test/conversion.jl | 36 ++++++++++++++++- test/test_conversions.jl | 42 ++++++++++++++++++++ 8 files changed, 154 insertions(+), 19 deletions(-) diff --git a/Project.toml b/Project.toml index 2cc6625a..9fc0b915 100644 --- a/Project.toml +++ b/Project.toml @@ -8,7 +8,7 @@ FixedPointNumbers = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" [compat] -ColorTypes = "0.10, 0.11" +ColorTypes = "0.11.5" FixedPointNumbers = "0.6, 0.7, 0.8" Reexport = "0.2, 1.0" julia = "1" diff --git a/docs/crosssectionalcharts.jl b/docs/crosssectionalcharts.jl index 6059454a..2adbb39e 100644 --- a/docs/crosssectionalcharts.jl +++ b/docs/crosssectionalcharts.jl @@ -184,6 +184,12 @@ crosssection(::Type{LCHab}) = crosssection(LCHab, x=(2, "C*", 0:100), crosssection(::Type{LCHuv}) = crosssection(LCHuv, x=(2, "C*", 0:100), y=(1, "L*", 0:100), z=(3, "H", 0:360)) +crosssection(::Type{Oklab}) = crosssection(Oklab, x=(2, "a", -0.4:0.01:0.4), + y=(3, "b", -0.4:0.01:0.4), + z=(1, "L", 0:1)) +crosssection(::Type{Oklch}) = crosssection(Oklch, x=(2, "C", 0:0.01:0.4), + y=(1, "L", 0:1), + z=(3, "h", 0:360)) crosssection(::Type{YIQ}) = crosssection(YIQ, x=(2, "I", -1:1), y=(3, "Q", -1:1), diff --git a/docs/src/constructionandconversion.md b/docs/src/constructionandconversion.md index 249bbff9..b5724da0 100644 --- a/docs/src/constructionandconversion.md +++ b/docs/src/constructionandconversion.md @@ -34,6 +34,13 @@ CrossSectionalCharts.crosssection(LCHab) # hide ```@example cross CrossSectionalCharts.crosssection(LCHuv) # hide ``` +- `Oklab`, `Oklch` and their transparent variants +```@example cross +CrossSectionalCharts.crosssection(Oklab) # hide +``` +```@example cross +CrossSectionalCharts.crosssection(Oklch) # hide +``` - `DIN99`, `DIN99d`, `DIN99o` and all 6 transparent variants - Storage formats `YIQ`, `YCbCr` and their transparent variants diff --git a/src/conversions.jl b/src/conversions.jl index e1764c27..dd9afdd2 100644 --- a/src/conversions.jl +++ b/src/conversions.jl @@ -60,10 +60,11 @@ 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 -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{RGB}, c::Union{LCHab{T}, LCHuv{T}, Oklch{T}}) where {T} = cnvt(RGB{T}, cnvt(XYZ{T}, c)) +convert(::Type{RGB{T}}, c::Union{LCHab{T}, LCHuv{T}, Oklch{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)) +convert(::Type{Oklab{T}}, c::RGB{T}) where {T} = cnvt(Oklab{T}, cnvt(XYZ{T}, c)) # Fallback to catch undefined operations cnvt(::Type{C}, c::TransparentColor) where {C<:Color} = cnvt(C, color(c)) @@ -202,9 +203,9 @@ end # To avoid stack overflow, the source types which do not support direct or # indirect conversion to RGB should be rejected. -cnvt(::Type{CV}, c::Union{LMS, xyY} ) where {CV<:AbstractRGB} = cnvt(CV, cnvt(XYZ{eltype(c)}, c)) -cnvt(::Type{CV}, c::Union{Lab, Luv, LCHab, LCHuv}) where {CV<:AbstractRGB} = cnvt(CV, cnvt(XYZ{eltype(c)}, c)) -cnvt(::Type{CV}, c::Union{DIN99d, DIN99o, DIN99} ) where {CV<:AbstractRGB} = cnvt(CV, cnvt(XYZ{eltype(c)}, c)) +cnvt(::Type{CV}, c::Union{LMS, xyY}) where {CV<:AbstractRGB} = cnvt(CV, cnvt(XYZ{eltype(c)}, c)) +cnvt(::Type{CV}, c::Union{Lab, Luv, Oklab, LCHab, LCHuv, Oklch}) where {CV<:AbstractRGB} = cnvt(CV, cnvt(XYZ{eltype(c)}, c)) +cnvt(::Type{CV}, c::Union{DIN99d, DIN99o, DIN99}) where {CV<:AbstractRGB} = cnvt(CV, cnvt(XYZ{eltype(c)}, c)) @noinline function cnvt(::Type{CV}, @nospecialize(c::Color)) where {CV<:AbstractRGB} error("No conversion of ", c, " to ", CV, " has been defined") end @@ -428,6 +429,14 @@ function cnvt(::Type{XYZ{T}}, c::LMS) where T @mul3x3 XYZ{T} CAT02_INV c.l c.m c.s end + +function cnvt(::Type{XYZ{T}}, c::Oklab) where T + lmsp = @mul3x3 LMS{T} M_OKLMSP2OKLAB_INV c.l c.a c.b + @mul3x3 XYZ{T} M_XYZ2OKLMS_INV lmsp.l^3 lmsp.m^3 lmsp.s^3 +end + +cnvt(::Type{XYZ{T}}, c::Oklch) where {T} = cnvt(XYZ{T}, cnvt(Oklab{T}, c)) + cnvt(::Type{XYZ{T}}, c::Union{LCHab, DIN99, DIN99o}) where {T} = cnvt(XYZ{T}, cnvt(Lab{T}, c)) cnvt(::Type{XYZ{T}}, c::LCHuv) where {T} = cnvt(XYZ{T}, cnvt(Luv{T}, c)) cnvt(::Type{XYZ{T}}, c::Color) where {T} = cnvt(XYZ{T}, convert(RGB{T}, c)::RGB{T}) @@ -593,6 +602,44 @@ end cnvt(::Type{LCHab{T}}, c::Color) where {T} = cnvt(LCHab{T}, convert(Lab{T}, c)::Lab{T}) +# Everything to Oklab +# ------------------- + +# Matrices as specified in https://bottosson.github.io/posts/oklab/ +const M_XYZ2OKLMS = Mat3x3([0.8189330101 0.3618667424 -0.1288597137 + 0.0329845436 0.9293118715 0.0361456387 + 0.0482003018 0.2643662691 0.6338517070]) + +const M_XYZ2OKLMS_INV = Mat3x3(inv(Float64.(M_XYZ2OKLMS))) + +const M_OKLMSP2OKLAB = Mat3x3([0.2104542553 0.7936177850 -0.0040720468 + 1.9779984951 -2.4285922050 0.4505937099 + 0.0259040371 0.7827717662 -0.8086757660]) + +const M_OKLMSP2OKLAB_INV = Mat3x3(inv(Float64.(M_OKLMSP2OKLAB))) + +function cnvt(::Type{Oklab{T}}, c::XYZ) where T + lms = @mul3x3 LMS{T} M_XYZ2OKLMS c.x c.y c.z + @mul3x3 Oklab{T} M_OKLMSP2OKLAB cbrt(lms.l) cbrt(lms.m) cbrt(lms.s) +end + +function cnvt(::Type{Oklab{T}}, c::Oklch) where T + Oklab{T}(c.l, polar_to_cartesian(c.c, c.h)...) +end + +cnvt(::Type{Oklab{T}}, c::Color) where {T} = cnvt(Oklab{T}, convert(XYZ{T}, c)::XYZ{T}) + + +# Everything to Oklch +# ------------------- + +function cnvt(::Type{Oklch{T}}, c::Oklab) where T + Oklch{T}(c.l, chroma(c), hue(c)) +end + +cnvt(::Type{Oklch{T}}, c::Color) where {T} = cnvt(Oklch{T}, convert(Oklab{T}, c)::Oklab{T}) + + # Everything to DIN99 # ------------------- diff --git a/src/precompile.jl b/src/precompile.jl index 61155a88..6f24fbb3 100644 --- a/src/precompile.jl +++ b/src/precompile.jl @@ -6,7 +6,7 @@ function _precompile_() cctypes = (Gray24, AGray32, RGB24, ARGB32) # non-parametric colors # conversions ## from/to XYZ - for T in feltypes, C in (HSV,LCHab,LCHuv,Lab,Luv) + for T in feltypes, C in (HSV,LCHab,LCHuv,Lab,Luv,Oklab,Oklch) precompile(Tuple{typeof(convert),Type{C{T}},XYZ{T}}) precompile(Tuple{typeof(convert),Type{XYZ{T}},C{T}}) end @@ -15,7 +15,7 @@ function _precompile_() precompile(Tuple{typeof(convert),Type{XYZ{T}},RGB{F}}) end ## to RGB - for T in eltypes, F in feltypes, C in (HSV,LCHab,LCHuv,Lab,Luv) + for T in eltypes, F in feltypes, C in (HSV,LCHab,LCHuv,Lab,Luv,Oklab,Oklch) precompile(Tuple{typeof(convert),Type{RGB{T}},C{F}}) end # parse diff --git a/src/utilities.jl b/src/utilities.jl index 7581b569..afb9d081 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -187,9 +187,10 @@ atan360(y, x) = (a = atand(y, x); signbit(a) ? oftype(a, a + 360) : a) return dd end -# override only the `Lab` and `Luv` versions just for now +# override only the `Lab`, `Luv`, and `Oklab` versions just for now @inline ColorTypes.hue(c::Lab) = atan360(c.b, c.a) @inline ColorTypes.hue(c::Luv) = atan360(c.v, c.u) +@inline ColorTypes.hue(c::Oklab) = atan360(c.b, c.a) @inline function sin_kernel(x::Float64) x * @evalpoly(x^2, @@ -376,8 +377,8 @@ hue, in degrees, in [0, 360]. The normalization is essentially equivalent to normalize_hue(c::C) where {C <: Union{HSV, HSL, HSI}} = C(normalize_hue(c.h), c.s, comp3(c)) normalize_hue(c::C) where {Cb <: Union{HSV, HSL, HSI}, C <: Union{AlphaColor{Cb}, ColorAlpha{Cb}}} = C(normalize_hue(c.h), c.s, comp3(c), alpha(c)) -normalize_hue(c::C) where C <: Union{LCHab, LCHuv} = C(c.l, c.c, normalize_hue(c.h)) -normalize_hue(c::C) where {Cb <: Union{LCHab, LCHuv}, C <: Union{AlphaColor{Cb}, ColorAlpha{Cb}}} = +normalize_hue(c::C) where C <: Union{LCHab, LCHuv, Oklch} = C(c.l, c.c, normalize_hue(c.h)) +normalize_hue(c::C) where {Cb <: Union{LCHab, LCHuv, Oklch}, C <: Union{AlphaColor{Cb}, ColorAlpha{Cb}}} = C(c.l, c.c, normalize_hue(c.h), c.alpha) """ @@ -401,7 +402,7 @@ function mean_hue(h1::T, h2::T) where {T <: Real} mh = muladd(F(0.5), d, hmin) return mh < 0 ? mh + 360 : mh end -@inline function mean_hue(a::C, b::C) where {Cb <: Union{Lab, Luv}, +@inline function mean_hue(a::C, b::C) where {Cb <: Union{Lab, Luv, Oklab}, C <: Union{Cb, AlphaColor{Cb}, ColorAlpha{Cb}}} a1, b1, a2, b2 = comp2(a), comp3(a), comp2(b), comp3(b) c1, c2 = chroma(a), chroma(b) @@ -421,7 +422,7 @@ end mb = muladd(k2, b1, k1 * b2) hue(Cb(zero(ma), ma, mb)) end -function mean_hue(a::C, b::C) where {Cb <: Union{LCHab, LCHuv}, +function mean_hue(a::C, b::C) where {Cb <: Union{LCHab, LCHuv, Oklch}, C <: Union{Cb, AlphaColor{Cb}, ColorAlpha{Cb}}} mean_hue(a.c == 0 ? b.h : a.h, b.c == 0 ? a.h : b.h) end @@ -435,7 +436,7 @@ _delta_h_th(T) = zero(T) _delta_h_th(::Type{Float32}) = 0.1f0 _delta_h_th(::Type{Float64}) = 6.5e-3 -function delta_h(a::C, b::C) where {Cb <: Union{Lab, Luv}, +function delta_h(a::C, b::C) where {Cb <: Union{Lab, Luv, Oklab}, C <: Union{Cb, AlphaColor{Cb}, ColorAlpha{Cb}}} a1, b1, a2, b2 = comp2(a), comp3(a), comp2(b), comp3(b) c1, c2 = chroma(a), chroma(b) @@ -458,7 +459,7 @@ function delta_h(a::C, b::C) where {Cb <: Union{Lab, Luv}, return @fastmath sqrt(c1 * c2) * sn end end -function delta_h(a::C, b::C) where {Cb <: Union{LCHab, LCHuv}, +function delta_h(a::C, b::C) where {Cb <: Union{LCHab, LCHuv, Oklch}, C <: Union{Cb, AlphaColor{Cb}, ColorAlpha{Cb}}} dh0 = hue(a) - hue(b) sh = muladd(dh0, oftype(dh0, 1 / 360), oftype(dh0, 0.5)) @@ -467,7 +468,7 @@ function delta_h(a::C, b::C) where {Cb <: Union{LCHab, LCHuv}, end delta_h(a, b) = delta_h(promote(a, b)...) -@inline function delta_c(a::C, b::C) where {Cb <: Union{Lab{Float32}, Luv{Float32}}, +@inline function delta_c(a::C, b::C) where {Cb <: Union{Lab{Float32}, Luv{Float32}, Oklab{Float32}}, C <: Union{Cb, AlphaColor{Cb}, ColorAlpha{Cb}}} n1, m1 = @fastmath minmax(comp2(a)^2, comp3(a)^2) n2, m2 = @fastmath minmax(comp2(b)^2, comp3(b)^2) @@ -482,7 +483,7 @@ Returns the color `w1*c1 + (1-w1)*c2` that is the weighted mean of `c1` and `c2`, where `c1` has a weight 0 ≤ `w1` ≤ 1. """ weighted_color_mean(w1::Real, c1::Colorant, c2::Colorant) = _weighted_color_mean(w1, c1, c2) -function weighted_color_mean(w1::Real, c1::C, c2::C) where {Cb <: Union{HSV, HSL, HSI, LCHab, LCHuv}, +function weighted_color_mean(w1::Real, c1::C, c2::C) where {Cb <: Union{HSV, HSL, HSI, LCHab, LCHuv, Oklch}, C <: Union{Cb, AlphaColor{Cb}, ColorAlpha{Cb}}} normalize_hue(_weighted_color_mean(w1, c1, c2)) end diff --git a/test/conversion.jl b/test/conversion.jl index 6236bf75..61a0d6e7 100644 --- a/test/conversion.jl +++ b/test/conversion.jl @@ -5,8 +5,8 @@ using ColorTypes: eltype_default # The filter mechanism should not be removed, as new types may be added in the future. const supported_p3 = filter(ColorTypes.parametric3) do C sym = Symbol(C) - # TODO: Remove the following exclusions when the color types are supported. - sym in (:Oklab, :Oklch) && return false + # NOTE: Add any currently unsupported color types here. + # sym in (:unsupported1, :unsupported2) && return false return true end if supported_p3 == ColorTypes.parametric3 @@ -236,6 +236,32 @@ end @test convert(RGB{Float64}, HSI{BigFloat}(-360120, .5, .5)) ≈ RGB{Float64}(.25,.25,1) end + # Oklab examples from Björn Ottosson (https://bottosson.github.io/posts/oklab/) + @testset "Oklab <-> XYZ" begin + @test convert(Oklab, XYZ{Float32}(0.950, 1.000, 1.089)) ≈ Oklab{Float32}(1.000, 0.000, 0.000) atol=2e-3 + @test convert(Oklab, XYZ{Float32}(1.000, 0.000, 0.000)) ≈ Oklab{Float32}(0.450, 1.236, -0.019) atol=2e-3 + @test convert(Oklab, XYZ{Float32}(0.000, 1.000, 0.000)) ≈ Oklab{Float32}(0.922, -0.671, 0.263) atol=2e-3 + @test convert(Oklab, XYZ{Float32}(0.000, 0.000, 1.000)) ≈ Oklab{Float32}(0.153, -1.415, -0.449) atol=2e-3 + + @test convert(XYZ, Oklab{Float32}(1.000, 0.000, 0.000)) ≈ XYZ{Float32}(0.950, 1.000, 1.089) atol=2e-3 + @test convert(XYZ, Oklab{Float32}(0.450, 1.236, -0.019)) ≈ XYZ{Float32}(1.000, 0.000, 0.000) atol=2e-3 + @test convert(XYZ, Oklab{Float32}(0.922, -0.671, 0.263)) ≈ XYZ{Float32}(0.000, 1.000, 0.000) atol=2e-3 + @test convert(XYZ, Oklab{Float32}(0.153, -1.415, -0.449)) ≈ XYZ{Float32}(0.000, 0.000, 1.000) atol=2e-3 + end + + # Selection of Oklch tests from the WPT test suite (https://wpt.fyi/results/css/css-color) + @testset "Oklch <-> RGB" begin + @test convert(Oklch, RGB{Float32}(0.00000, 0.00000, 0.00000)) ≈ Oklch{Float32}(0.00, 0.00, 0) rtol=2e-2 + @test convert(Oklch, RGB{Float32}(0.23056, 0.31730, 0.82628)) ≈ Oklch{Float32}(0.50, 0.20, 270) rtol=2e-2 + @test convert(Oklch, RGB{Float32}(0.32022, 0.85805, 0.61147)) ≈ Oklch{Float32}(0.80, 0.15, 160) rtol=2e-2 + @test convert(Oklch, RGB{Float32}(0.67293, 0.27791, 0.52280)) ≈ Oklch{Float32}(0.55, 0.15, 345) rtol=2e-2 + + @test convert(RGB, Oklch{Float32}(0.00, 0.00, 0)) ≈ RGB{Float32}(0.00000, 0.00000, 0.00000) rtol=2e-2 + @test convert(RGB, Oklch{Float32}(0.50, 0.20, 270)) ≈ RGB{Float32}(0.23056, 0.31730, 0.82628) rtol=2e-2 + @test convert(RGB, Oklch{Float32}(0.80, 0.15, 160)) ≈ RGB{Float32}(0.32022, 0.85805, 0.61147) rtol=2e-2 + @test convert(RGB, Oklch{Float32}(0.55, 0.15, 345)) ≈ RGB{Float32}(0.67293, 0.27791, 0.52280) rtol=2e-2 + end + @testset "custom types" begin # issue #465 @test_throws ErrorException convert(RGB, C3{Float32}(1, 2, 3)) @@ -271,9 +297,15 @@ end function diffnorm(a::T, b::T) where {T<:Union{Lab,Luv}} sqrt(sqd(a.l, b.l, 100) + sqd(comp2(a), comp2(b), 200) + sqd(comp3(a), comp3(b), 200))/sqrt(3) end + function diffnorm(a::T, b::T) where {T<:Oklab} + sqrt(sqd(a.l, b.l) + sqd(a.a, b.a, 0.4) + sqd(a.b, b.b, 0.4))/sqrt(3) + end function diffnorm(a::T, b::T) where {T<:Union{LCHab,LCHuv}} sqrt(sqd(a.l, b.l, 100) + sqd(a.c, b.c, 100) + sqd(a.h, b.h, 360))/sqrt(3) end + function diffnorm(a::T, b::T) where {T<:Oklch} + sqrt(sqd(a.l, b.l, 1) + sqd(a.c, b.c, 0.4) + sqd(a.h, b.h, 360))/sqrt(3) + end function diffnorm(a::T, b::T) where {T<:Union{DIN99,DIN99d,DIN99o}} # csconv has no DIN99 case sqrt(sqd(a.l, b.l, 100) + sqd(a.a, b.a, 100) + sqd(a.b, b.b, 100))/sqrt(3) end diff --git a/test/test_conversions.jl b/test/test_conversions.jl index 4c3933fc..f99d789d 100644 --- a/test/test_conversions.jl +++ b/test/test_conversions.jl @@ -104,6 +104,48 @@ LCHab{Float64}(44.97666070400284,73.18288064267016,343.7064570495635), LCHab{Float64}(60.664366637100485,50.33163619069481,134.3624622173942), LCHab{Float64}(17.93658797983788,19.25047865923466,4.326814913733189), LCHab{Float64}(22.70092488166712,79.69128178686806,301.8881183495275)], + Oklab{Float64} => Oklab{Float64}[ +Oklab{Float64}(0.6473642587850058, -0.16640845649540867, 0.12765220713589567), +Oklab{Float64}(0.6282157000609071, 0.08183520074853895, 0.11452219273635467), +Oklab{Float64}(0.5091740287281353, -0.07733208893501779, 0.08085693619818175), +Oklab{Float64}(0.669802095404347, -0.04346788322947211, 0.11796988019003865), +Oklab{Float64}(0.6208435577510582, 0.2344081686546006, 0.06775684070354147), +Oklab{Float64}(0.46482609679999626, 0.03810249957927366, 0.012580885182442119), +Oklab{Float64}(0.7830976646978222, -0.07410740013992041, 0.14346504139165858), +Oklab{Float64}(0.7546751782330786, -0.026128400789299126, 0.1300180919169248), +Oklab{Float64}(0.4962641797550877, 0.0949917024525385, -0.23911049231795142), +Oklab{Float64}(0.5315894235973362, 0.18443388521737486, -0.13286781075363044), +Oklab{Float64}(0.3233886953836771, 0.004596623887945836, -0.181588589534855), +Oklab{Float64}(0.5991424390294658, 0.22132732851253745, 0.09690369827995632), +Oklab{Float64}(0.4767148234855445, -0.08670766827163175, 0.05896925338549625), +Oklab{Float64}(0.6443546915598534, -0.1459141388694792, 0.12863594238830386), +Oklab{Float64}(0.8157342282735532, 0.03810437558741276, -0.0592931671223586), +Oklab{Float64}(0.6328737374708884, 0.004205728077657682, -0.06868954690353435), +Oklab{Float64}(0.5551261175856945, 0.2139904180324278, -0.05463476841811064), +Oklab{Float64}(0.6488416605810022, -0.09659369398846307, 0.08652002524801804), +Oklab{Float64}(0.3000522124257983, 0.058284478234404524, 0.0039986286050455655), +Oklab{Float64}(0.3510586258806637, -0.015952114291585485, -0.196300479928)], + Oklch{Float64} => Oklch{Float64}[ +Oklch{Float64}(0.6473642587850058, 0.20973044695477558, 142.50812592692188), +Oklch{Float64}(0.6282157000609071, 0.1407562883522311, 54.45118126144123), +Oklch{Float64}(0.5091740287281353, 0.1118842978724465, 133.7235179145194), +Oklch{Float64}(0.669802095404347, 0.1257233053355785, 110.2271240721123), +Oklch{Float64}(0.6208435577510582, 0.24400446511104826, 16.122199378967657), +Oklch{Float64}(0.46482609679999626, 0.04012579153315659, 18.272463586642523), +Oklch{Float64}(0.7830976646978222, 0.16147484279914498, 117.31878299737747), +Oklch{Float64}(0.7546751782330786, 0.13261748585131655, 101.3627962996733), +Oklch{Float64}(0.4962641797550877, 0.25728826454264236, 291.666497387129), +Oklch{Float64}(0.5315894235973362, 0.2273097295560363, 324.23068650784376), +Oklch{Float64}(0.3233886953836771, 0.1816467582986971, 271.4500410993016), +Oklch{Float64}(0.5991424390294658, 0.24161149204214083, 23.645235814467426), +Oklch{Float64}(0.4767148234855445, 0.10485987117074957, 145.7806655807504), +Oklch{Float64}(0.6443546915598534, 0.19452028582168157, 138.60103298820795), +Oklch{Float64}(0.8157342282735532, 0.0704813670859651, 302.7265689301318), +Oklch{Float64}(0.6328737374708884, 0.06881818075535014, 273.503735752975), +Oklch{Float64}(0.5551261175856945, 0.22085483225366337, 345.6775522473198), +Oklch{Float64}(0.6488416605810022, 0.1296767384200209, 138.14884921190213), +Oklch{Float64}(0.3000522124257983, 0.05842148092763366, 3.924648887029628), +Oklch{Float64}(0.3510586258806637, 0.19694757772142044, 265.3541385866738)], LMS{Float64} => LMS{Float64}[ LMS{Float64}(0.2307455933908811,0.3966129226546093,0.06205539564220773), LMS{Float64}(0.3107019890330763,0.1897841714619035,0.0498207460521362),