Add wide gamut color support with OKLCH and LAB formats

Implements modern wide gamut color formats with CSS Color Module Level 4
gamut mapping, addressing PR feedback with Go implementation, performance
benchmarks, and reorganized documentation.

Features:
- OKLCH (perceptually uniform color space)
- CIE LAB (device-independent color space)
- CSS Color 4 compliant gamut mapping algorithm
- Inline comment support in color config parsing

Addressing PR Feedback:

1. Go Implementation (tools/utils/style/):
   - Complete OKLCH and LAB parsing with gamut mapping
   - Matches Python implementation structure
   - Comprehensive test suite (all tests passing)
   - Performance benchmarks showing acceptable overhead

2. Performance Benchmarks:
   - OKLCH: ~4.6 µs/op
   - LAB: ~1.5 µs/op
   - 10 mixed colors: ~13 µs total
   - Typical config (50 colors): <0.5ms startup impact

3. Documentation Reorganization:
   - Moved detailed color docs to docs/wide-gamut-colors.rst
   - Configuration docs now link to separate documentation
   - Reduces size of main configuration documentation

Gamut Mapping:
- Binary search chroma reduction from CSS Color Module Level 4
- Preserves lightness and hue while reducing chroma for out-of-gamut colors
- Uses deltaE OK (JND threshold: 0.02) for perceptual difference
- Ensures graceful degradation on sRGB displays

Python Implementation:
- parse_oklch(): OKLCH color parsing with gamut mapping
- parse_lab(): CIE LAB parsing with gamut mapping via OKLCH conversion
- lab_to_oklch(): LAB to OKLCH conversion for consistent gamut mapping
- oklch_to_srgb_gamut_map(): CSS Color 4 gamut mapping algorithm
- srgb_to_oklab(): Reverse conversion for deltaE calculations
- deltaE_ok(): Perceptual color difference in OKLab space

Go Implementation:
- colorspaces.go: All color space conversions and gamut mapping
- wrapper.go: ParseColor() updated to support OKLCH and LAB
- Comprehensive test coverage with benchmarks
- Matches Python implementation behavior

Robustness:
- NaN and infinity validation in all color parsing functions
- Defense-in-depth with validation at parsing and gamut mapping levels
- Returns None/error for invalid input (consistent error handling)
- Validates before clamping operations to prevent NaN propagation

Files changed:
- Python: kitty/rgb.py, kitty_tests/datatypes.py (+250 lines)
- Go: tools/utils/style/colorspaces.go, wrapper.go (+350 lines, tests)
- Docs: docs/wide-gamut-colors.rst (moved from inline)
- Config: kitty/options/definition.py (simplified, links to docs)

References:
- CSS Color Module Level 4: https://www.w3.org/TR/css-color-4/
- OKLCH Color Space: https://bottosson.github.io/posts/oklab/

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Jökull Sólberg
2025-12-25 15:36:23 +00:00
parent 0fb54d32b1
commit 64abd87a9e
8 changed files with 1269 additions and 1 deletions

View File

@@ -148,6 +148,22 @@ func parse_rgb(color string) (ans RGBA, err error) {
}
func ParseColor(color string) (RGBA, error) {
// Strip inline comments (e.g., "oklch(...) # comment")
// For hex colors like "#ff0000", preserve the first #, but strip comments after spaces
color = strings.TrimSpace(color)
if strings.HasPrefix(color, "#") {
// For hex colors, only strip comments after whitespace
parts := strings.Fields(color)
if len(parts) > 0 {
color = parts[0] // Keep only the hex color part
}
} else {
// For non-hex colors, strip everything after #
if idx := strings.Index(color, "#"); idx >= 0 {
color = strings.TrimSpace(color[:idx])
}
}
raw := strings.TrimSpace(strings.ToLower(color))
if val, ok := ColorNames[raw]; ok {
return val, nil
@@ -155,6 +171,12 @@ func ParseColor(color string) (RGBA, error) {
if strings.HasPrefix(raw, "#") {
return parse_sharp(raw[1:])
}
if strings.HasPrefix(raw, "oklch(") {
return parseOklch(raw[6:])
}
if strings.HasPrefix(raw, "lab(") {
return parseLab(raw[4:])
}
if strings.HasPrefix(raw, "rgb:") {
return parse_rgb(raw[4:])
}