From 8474f0746ab5ecc957111b563cbd2e03873b4435 Mon Sep 17 00:00:00 2001 From: kimikage Date: Sat, 8 May 2021 16:49:42 +0900 Subject: [PATCH] Fix `DE_94` & `DE_CMC` color difference metrics (#461) This makes `DE_94` and `DE_CMC` quasimetric. Within those metrics, the first argument of `colordiff` is taken as the reference color. This adds the weight parameters `k1` and `k2` to `DE_94`. This also makes the omission of the metric constructor arguments more flexible. --- src/differences.jl | 82 +++++++++++++++++++++++++--------------------- test/colordiff.jl | 14 +++++--- 2 files changed, 54 insertions(+), 42 deletions(-) diff --git a/src/differences.jl b/src/differences.jl index b440740b..cd484b60 100644 --- a/src/differences.jl +++ b/src/differences.jl @@ -10,34 +10,52 @@ struct DE_2000 <: DifferenceMetric kl::Float64 kc::Float64 kh::Float64 + DE_2000(kl=1, kc=1, kh=1) = new(kl, kc, kh) end """ - DE_2000() - DE_2000(kl, kc, kh) + DE_2000(kl=1, kc=1, kh=1) Construct a metric of the CIE Delta E 2000 recommendation, with weighting parameters `kl`, `kc` and `kh` as provided for in the recommendation. When not provided, these parameters default to `1`. """ -DE_2000() = DE_2000(1,1,1) +DE_2000() struct DE_94 <: DifferenceMetric kl::Float64 kc::Float64 kh::Float64 + k1::Float64 + k2::Float64 + DE_94(kl=1, kc=1, kh=1, k1=0.045, k2=0.015) = new(kl, kc, kh, k1, k2) end """ - DE_94() - DE_94(kl, kc, kh) + DE_94(kl=1, kc=1, kh=1, k1=0.045, k2=0.015) Construct a metric of CIE Delta E 94 recommendation (1994), with weighting -parameters `kl`, `kc` and `kh` as provided for in the recommendation. When not -provided, these parameters default to `1`. +parameters `kl`, `kc`, `kh`, `k1`, and `k2` as provided for in the +recommendation. The `kl`, `k1`, and `k2` depend on the application: + +| |Graphic Arts|Textiles| +|:--:|:-----------|:-------| +|`kl`|`1` |`2` | +|`k1`|`0.045` |`0.048` | +|`k2`|`0.015` |`0.014` | + +and the default values are for graphic arts. The `kc` and `kh` default to `1`. + The `DE_94` is more perceptually uniform than the [`DE_AB`](@ref), but has some non-uniformities resolved by the [`DE_2000`](@ref). + +!!! note + The `DE_94` is a quasimetric, i.e. violates symmetry. Therefore, + `colordiff(a, b, metric=DE_94())` may not equal to + `colordiff(b, a, metric=DE_94())`. + The first argument of `colordiff` is taken as the reference (standard) + color. """ -DE_94() = DE_94(1,1,1) +DE_94() struct DE_JPC79 <: DifferenceMetric @@ -55,38 +73,39 @@ DE_JPC79() struct DE_CMC <: DifferenceMetric kl::Float64 kc::Float64 + DE_CMC(kl=1, kc=1) = new(kl, kc) end """ - DE_CMC() - DE_CMC(kl, kc) + DE_CMC(kl=1, kc=1) Construct a metric using the CMC equation (CMC l:c), with weighting parameters `kl` and `kc`. When not provided, these parameters default to `1`. + !!! note The `DE_CMC` is a quasimetric, i.e. violates symmetry. Therefore, `colordiff(a, b, metric=DE_CMC())` may not equal to `colordiff(b, a, metric=DE_CMC())`. + The first argument of `colordiff` is taken as the reference (standard) + color. """ -DE_CMC() = DE_CMC(1,1) +DE_CMC() struct DE_BFD <: DifferenceMetric wp::XYZ{Float64} kl::Float64 kc::Float64 + DE_BFD(wp::XYZ, kl=1, kc=1) = new(wp, kl, kc) end """ - DE_BFD() - DE_BFD([wp,] kl, kc) + DE_BFD([wp,] kl=1, kc=1) Construct a metric using the BFD equation, with weighting parameters `kl` and `kc`. Additionally, a whitepoint `wp` can be specified, because the BFD equation must convert between `XYZ` and `Lab` during the computation. When not provided, `kl` and `kc` default to `1`, and `wp` defaults to CIE D65 (`Colors.WP_D65`). """ -DE_BFD() = DE_BFD(WP_DEFAULT,1,1) -DE_BFD(kl, kc) = DE_BFD(WP_DEFAULT,kl, kc) - +DE_BFD(kl=1, kc=1) = DE_BFD(WP_DEFAULT, kl, kc) struct DE_AB <: EuclideanDifferenceMetric{Lab} @@ -244,13 +263,10 @@ function _colordiff(ai::Color, bi::Color, m::DE_94) # Calculate H* dh = 2 * sqrt(a.c * b.c) * sind(dh/2) - # Calculate geometric mean of chroma - mc = sqrt(a.c*b.c) - # Lightness, hue, chroma correction terms sl = 1 - sc = 1+0.045*mc - sh = 1+0.015*mc + sc = 1 + m.k1 * a.c + sh = 1 + m.k2 * a.c sqrt((dl/(m.kl*sl))^2 + (dc/(m.kc*sc))^2 + (dh/(m.kh*sh))^2) @@ -325,32 +341,22 @@ function _colordiff(ai::Color, bi::Color, m::DE_CMC) # Calculate H* from C*'s and h dh = 2 * sqrt(a.c * b.c) * sind(dh/2) - # Find the mean value of the inputs to use as the "standard" - ml, mc = (a.l + b.l)/2, (a.c + b.c)/2 # TODO: use `a.l` and `a.c` instead - - # Calculate mean hue value - if a.c * b.c == 0 - mh = a.h + b.h - else - mh = mean_hue(a.h, b.h) - end - # L* adjustment term if (a.l <= 16) sl = 0.511 else - sl = 0.040975*ml/(1+0.01765*ml) + sl = 0.040975 * a.l / (1 + 0.01765 * a.l) end # C* adjustment term - sc = 0.0638*mc/(1+0.0131*mc)+0.638 + sc = 0.0638 * a.c / (1 + 0.0131 * a.c) + 0.638 - f = sqrt((mc^4)/(mc^4 + 1900)) + f = sqrt(a.c^4 / (a.c^4 + 1900)) - if (mh >= 164 && mh < 345) - t = 0.56 + abs(0.2*cosd(mh+168)) + if 164 <= a.h <= 345 + t = 0.56 + abs(0.2 * cosd(a.h + 168)) else - t = 0.36 + abs(0.4*cosd(mh+35)) + t = 0.36 + abs(0.4 * cosd(a.h + 35)) end # H* adjustment term @@ -365,7 +371,7 @@ end function _colordiff(ai::Color, bi::Color, m::DE_BFD) # Currently, support for the `wp` argument of `convert` is limited. function to_xyz(c::Color, wp) - c isa XYZ && return ai + c isa XYZ && return c (c isa xyY || c isa LMS) && return convert(XYZ, c) (c isa Lab || c isa Luv) && return convert(XYZ, c, wp) c isa LCHuv && return convert(XYZ, convert(Luv, c), wp) diff --git a/test/colordiff.jl b/test/colordiff.jl index 4775aee1..d1e75747 100644 --- a/test/colordiff.jl +++ b/test/colordiff.jl @@ -1,4 +1,4 @@ -using Colors, Test +using Colors, Test, FixedPointNumbers # Test the colordiff function against example input and output from: # @@ -61,6 +61,7 @@ using Colors, Test jl_purple = Lab{Float32}(Colors.JULIA_LOGO_COLORS.purple) colors = (jl_red, jl_green, jl_blue, jl_purple) pairs = ((a, b) for a in colors for b in Iterators.filter(c -> c != a,colors)) + tri = (colorant"navy", colorant"peru", colorant"gold") @testset "properties of metrics" begin metrics = (DE_2000(), DE_94(), DE_JPC79(), DE_CMC(), DE_BFD(), @@ -72,17 +73,22 @@ using Colors, Test # positivity @test all(p -> colordiff(p[1], p[2]; metric=metric) > 0, pairs) # symmetry - if metric isa DE_CMC # quasimetric - # TODO: add test + if metric isa DE_94 || metric isa DE_CMC # quasimetric + @test all(p -> abs(colordiff(p[1], p[2]; metric=metric) - + colordiff(p[2], p[1]; metric=metric)) > eps_cdiff, pairs) else @test all(p -> abs(colordiff(p[1], p[2]; metric=metric) - colordiff(p[2], p[1]; metric=metric)) < eps_cdiff, pairs) end # triangle inequality - metric isa DE_CMC && continue # FIXME @test all(p -> colordiff(p[1], p[2]; metric=metric) <= colordiff(p[1], jl_red; metric=metric) + colordiff(jl_red, p[2]; metric=metric), pairs) + if any(m -> metric isa m, (DE_2000, DE_94, DE_CMC)) # semimetric + @test colordiff(tri[1], tri[3]; metric=metric) > + colordiff(tri[1], tri[2]; metric=metric) + + colordiff(tri[2], tri[3]; metric=metric) + end end end