ThemeProvider/SemanticColorMapper.cs has two related defects in the single-source-colour path (ExtrapolateColorToLightness and its chroma search).
1. Alpha is discarded
At :286 and :323 the code calls Color.FromOklab(...) without an alpha argument, so alpha defaults to 1.0 and baseColor.A is lost. The interpolation path (MixOklab) does keep alpha. The result is that every meaning with a single colour, which includes every accent, comes out opaque, while neutral ramps keep their alpha.
Repro: a custom theme with Neutral = [#10101080, #f0f0f080] and Primary = [#3366cc80]:
src primary alpha=0.50196; Neutral(Medium).A=0.50196; Primary(Medium).A=1
Expected: Primary(Medium).A is 0.502.
Fix: use Color.FromOklab(targetOklab, baseColor.A) and Color.FromOklab(testOklab, baseColor.A).
2. The gamut test has no tolerance
:289-291 and :325-327 check R >= 0.0 && R <= 1.0 exactly. An Oklab round trip leaves about 1e-7 of floating-point error. For example, #ff0000 comes back as linear (1.0000000004, -5e-9, -3e-8). Colours that are really in gamut are therefore classed as out of gamut, and the chroma binary search (tolerance 0.001) stops slightly inside the boundary.
Repro: a single-colour Primary mapped at its own lightness:
#ff0000 → #ff0201
#00ff00 → #04ff03
#ffff00 → #ffff08
Expected: the input colour is returned unchanged.
Fix: move the check into one helper that uses an epsilon, for example c >= -1e-6 && c <= 1 + 1e-6, keep the final clamp, and use the helper at both call sites.
Acceptance
- Accent colours keep their source alpha.
- Mapping a single colour at its own lightness returns that colour exactly, to 8-bit precision.
- Tests cover both.
ThemeProvider/SemanticColorMapper.cshas two related defects in the single-source-colour path (ExtrapolateColorToLightnessand its chroma search).1. Alpha is discarded
At
:286and:323the code callsColor.FromOklab(...)without an alpha argument, so alpha defaults to 1.0 andbaseColor.Ais lost. The interpolation path (MixOklab) does keep alpha. The result is that every meaning with a single colour, which includes every accent, comes out opaque, while neutral ramps keep their alpha.Repro: a custom theme with Neutral = [
#10101080,#f0f0f080] and Primary = [#3366cc80]:Expected:
Primary(Medium).Ais 0.502.Fix: use
Color.FromOklab(targetOklab, baseColor.A)andColor.FromOklab(testOklab, baseColor.A).2. The gamut test has no tolerance
:289-291and:325-327checkR >= 0.0 && R <= 1.0exactly. An Oklab round trip leaves about 1e-7 of floating-point error. For example,#ff0000comes back as linear (1.0000000004, -5e-9, -3e-8). Colours that are really in gamut are therefore classed as out of gamut, and the chroma binary search (tolerance 0.001) stops slightly inside the boundary.Repro: a single-colour Primary mapped at its own lightness:
#ff0000→#ff0201#00ff00→#04ff03#ffff00→#ffff08Expected: the input colour is returned unchanged.
Fix: move the check into one helper that uses an epsilon, for example
c >= -1e-6 && c <= 1 + 1e-6, keep the final clamp, and use the helper at both call sites.Acceptance