Skip to content

Commit

Permalink
Add Oklab and Oklch
Browse files Browse the repository at this point in the history
  • Loading branch information
eprovst committed Aug 1, 2023
1 parent f0a508b commit fd95b02
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 19 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ FixedPointNumbers = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"

[compat]
ColorTypes = "0.10, 0.11"
ColorTypes = "0.12"
FixedPointNumbers = "0.6, 0.7, 0.8"
Reexport = "0.2, 1.0"
julia = "1"
Expand Down
57 changes: 52 additions & 5 deletions src/conversions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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})
Expand Down Expand Up @@ -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
# -------------------

Expand Down
4 changes: 2 additions & 2 deletions src/precompile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
19 changes: 10 additions & 9 deletions src/utilities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)

"""
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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))
Expand All @@ -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)
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions test/conversion.jl
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,10 @@ end
function diffnorm(a::T, b::T) where {T<:Union{HSV,HSL,HSI}}
sqrt(sqd(a.h, b.h, 360) + sqd(a.s, b.s) + sqd(comp3(a), comp3(b)))/sqrt(3)
end
function diffnorm(a::T, b::T) where {T<:Union{Lab,Luv}}
function diffnorm(a::T, b::T) where {T<:Union{Lab,Luv,Oklab}}
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<:Union{LCHab,LCHuv}}
function diffnorm(a::T, b::T) where {T<:Union{LCHab,LCHuv,Oklch}}
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<:Union{DIN99,DIN99d,DIN99o}} # csconv has no DIN99 case
Expand Down
42 changes: 42 additions & 0 deletions test/test_conversions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down

0 comments on commit fd95b02

Please sign in to comment.