Bring src/oklch.gleam in line with CSS Color 4 / OKLab-OKLCH conversions, make gamut mapping actually effective, and lock behavior with reference-quality tests.
- Replace
rgb_to_oklch/1internals with canonical pipeline:- sRGB -> linear sRGB (correct transfer function)
- linear sRGB -> LMS (matrix)
- LMS -> nonlinearity (
cbrtper channel) - LMS' -> OKLab (matrix)
- OKLab -> OKLCH (
C = sqrt(a^2+b^2),h = atan2(b,a))
- Replace
oklch_to_rgb_clamped/1internals with inverse canonical pipeline:- OKLCH -> OKLab (
a = C*cos(h),b = C*sin(h)) - OKLab -> LMS' (inverse matrix)
- LMS' -> LMS (
^3) - LMS -> linear sRGB (inverse matrix)
- linear sRGB -> sRGB transfer function
- OKLCH -> OKLab (
- Keep alpha pass-through unchanged.
srgb_to_linear/1- Use branch threshold
0.04045(not divided by 12.92). - For upper branch use
((c + 0.055) / 1.055) ^ 2.4.
- Use branch threshold
linear_to_srgb/1- Use threshold
0.0031308. - For upper branch use
1.055 * (c ^ (1/2.4)) - 0.055.
- Use threshold
- Clamp output channel to
[0,1]only where intended (final public RGB output), not in internal gamut checks.
- Introduce an unclamped conversion path for internal checks (e.g.
oklch_to_rgb_raw/1) that can return values outside[0,1]. - Update
is_in_gamut/1to evaluate raw RGB channel bounds. - Update
clip_to_gamut/1to:- convert via raw path,
- clamp channels,
- convert back to OKLCH with corrected transforms.
- Keep
oklch_to_rgb/1behavior:- short-circuit black/white,
- if in gamut, return converted color,
- else run binary-search Local MINDE path.
- Verify
binary_search_chroma/8returns the searched chroma candidate (or clipped candidate) rather than defaulting back to unrelated original state at termination.
- Fix
format_chroma/1andformat_alpha/1to preserve leading zeros in decimals:0.05must serialize as"0.05", not"0.5".
- Ensure alpha hex in
rgb_to_hex/1is always two uppercase hex digits whenalpha < 1.0. - Optionally centralize two-digit hex formatting in a helper (
to_hex_2/1) used by all channels.
- Replace manual single-step wrap logic in
rotate_hue/2andset_h/2with the shared modulo-based normalization (clamp_h/1style), so large negative values normalize correctly.
- Add reference conversion tests with known-value fixtures:
#FF0000,#00FF00,#0000FF, neutral grays, and a wide-gamut-like high chroma case.
- Add round-trip accuracy thresholds:
RGB -> OKLCH -> RGBabsolute per-channel tolerance.
- Add dedicated gamut-mapping behavior tests:
- assert out-of-gamut input follows mapped path,
- assert mapped RGB channels end in
[0,1], - assert approximate lightness/hue preservation.
- Add CSS string edge cases:
- chroma/alpha values
0.01,0.05,0.10,1.00.
- chroma/alpha values
- Add hex alpha formatting tests:
- e.g. alpha
0.03,0.5,1.0.
- e.g. alpha
- Run
gleam testand ensure all legacy tests still pass or are updated for corrected math. - Add a small developer sanity script in
dev/oklch_dev.gleamto print a few known conversions for quick manual inspection. - Confirm docs/examples match final output formatting and behavior.
- Core math + transfer functions
- Gamut mapping plumbing
- Formatting/hex fixes
- Tests
- Final cleanup and docs touch-ups