Technical Deep Dive#
For the color nerds, math geeks, and optimization enthusiasts who want to know HOW the magic actually works
So you’ve used CM-Colors and thought “this is pretty neat, but HOW does it actually work?” Welcome to the rabbit hole! We’re about to get into some serious color science, perceptual mathematics, and optimization theory.
The Big Picture: What We’re Actually Optimizing#
When you give us two colors that don’t meet accessibility standards, we’re solving a constrained optimization problem:
Find the smallest perceptual change to your text color that achieves the required contrast ratio.
Sounds simple? It’s not. Here’s why this is mathematically fascinating:
The Color Space Problem#
Your RGB values (like #FF5733) are great for computers, terrible for human perception. When you change (255, 87, 51) to (250, 87, 51), that looks like a tiny change numerically, but perceptually it might be huge or imperceptible depending on the color.
Solution: We convert everything to OKLCH color space, which is designed to match human vision:
L (Lightness): How bright/dark the color appears
C (Chroma): How saturated/vivid the color is
H (Hue): The actual color (red, blue, etc.)
OKLCH is perceptually uniform, meaning equal numerical changes produce equal visual changes. This is crucial for making “minimal” adjustments that actually look minimal.
The Perceptual Distance Problem#
How do we measure if two colors “look similar”? Enter Delta E 2000 - the current state-of-the-art formula for color difference that considers:
How sensitive human eyes are to different hues
How lighting conditions affect perception
The fact that we’re better at detecting some color changes than others
Delta E < 1.0 = Changes most people can’t detect Delta E < 2.0 = Only noticeable when colors are side-by-side Delta E > 5.0 = Obviously different colors
We try to keep changes under 2.0 Delta E whenever possible.
Note: The function’s we are mentioning are all from
optimised.pyand is used to create ourtune_colors()function
The Algorithm: Multi-Phase Optimization#
Our generate_accessible_color() function uses a sophisticated multi-phase approach:
Phase 1: Binary Search on Lightness#
def binary_search_lightness(text_rgb, bg_rgb, delta_e_threshold=2.0, target_contrast=7.0):
Why lightness first? Most accessibility problems are solved by making text darker or lighter. This gives us the biggest contrast improvements with minimal perceptual change.
The binary search magic:
Convert your color to OKLCH
Determine if we need to go lighter or darker based on background
Binary search the lightness value (L component) in 20 iterations
Each iteration cuts the search space in half
Achieves ~1 million precision levels in just 20 steps
Mathematical complexity: O(log n) vs O(n) for brute force
Phase 2: Gradient Descent on Lightness + Chroma#
def gradient_descent_oklch(text_rgb, bg_rgb, delta_e_threshold=2.0, target_contrast=7.0):
If adjusting lightness alone isn’t enough, we optimize both lightness AND chroma (saturation) simultaneously using gradient descent.
The cost function we’re minimizing:
cost = contrast_penalty + delta_e_penalty + distance_penalty
# Where:
contrast_penalty = max(0, target_contrast - actual_contrast) * 1000
delta_e_penalty = max(0, delta_e - threshold) * 10000
distance_penalty = delta_e * 100
Why these specific weights?
10000x penalty for exceeding Delta E threshold (hard constraint)
1000x penalty for missing contrast target (primary goal)
100x penalty for perceptual distance (minimize brand impact)
Numerical gradient computation: We use central difference approximation because the color conversion functions aren’t analytically differentiable:
gradient[i] = (f(x + ε) - f(x - ε)) / (2ε)
Adaptive learning rate: Starts at 0.02, decays by 5% every 10 iterations to ensure convergence.
Phase 3: Progressive Delta E Relaxation#
If neither binary search nor gradient descent finds a solution within our strict Delta E threshold, we progressively relax constraints:
delta_e_sequence = [0.8, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.7, 3.0, 3.5, 4.0, 5.0]
We run both optimization phases at each threshold level, keeping the best candidate found so far.
The Color Science Behind the Scenes#
OKLCH Color Space Conversion#
OKLCH is based on the OKLAB color space, which was designed in 2020 to be more perceptually uniform than previous attempts. The conversion from RGB involves:
Linear RGB: Remove gamma correction
XYZ conversion: Transform to CIE XYZ color space
OKLAB transformation: Apply the OK color appearance model
Cylindrical coordinates: Convert to lightness-chroma-hue
Delta E 2000 Calculation#
The Delta E 2000 formula considers:
Lightness weighting: How sensitive we are to brightness changes
Chroma weighting: How saturation affects perceived difference
Hue weighting: Different sensitivity to hue shifts across the color wheel
Interaction terms: How lightness, chroma, and hue changes interact
It’s a beast of a formula involving elliptical parameters, rotation terms, and compensation factors. The math is gnarly, but the perceptual accuracy is worth it.
Contrast Ratio Mathematics#
WCAG contrast ratio is defined as:
contrast = (L1 + 0.05) / (L2 + 0.05)
Where L1 and L2 are the relative luminances of the lighter and darker colors respectively.
Target ratios:
AAA Large Text: 4.5:1 minimum
AA Normal Text: 4.5:1 minimum
AAA Normal Text: 7:1 minimum (our default target)
Performance Optimizations#
Why Not Brute Force?#
A naive brute force approach might test every possible RGB combination:
256³ = 16.7 million possible colors
Plus Delta E calculation for each = computationally expensive
Plus contrast ratio calculation = even slower
Our Optimized Approach#
Binary search: 20 iterations vs ~256 for linear search
OKLCH space: More efficient than RGB for perceptual operations
Gradient descent: Follows the mathematical gradient toward optimal solution
Early termination: Stop as soon as we find a valid solution
Progressive relaxation: Only try harder thresholds if needed
Result: ~100x faster than brute force with identical quality.
Edge Cases and Failure Modes#
When We Can’t Help You#
Some color combinations are just mathematically impossible to fix within reasonable perceptual bounds:
Neon yellow on white: Even perfect black text only gives ~17:1 contrast
Very similar colors: If your text and background are too close, we’d need to change them dramatically
Extreme saturation: Highly saturated colors have limited lightness range in RGB
Fallback Behavior#
When optimization fails:
Return the best candidate found (may not meet full contrast requirements)
Preserve original color if no improvement possible
Never return invalid RGB values
Always return something usable
Implementation Details#
Numerical Stability#
Color space conversions can be numerically unstable near the edges of the RGB gamut. We handle this with:
Safe conversion functions: Check for valid RGB output at each step
Gamut clamping: Keep intermediate values within valid ranges
Exception handling: Gracefully fail to fallback methods
Memory Efficiency#
No color caching: Each optimization is stateless
Minimal object allocation: Reuse data structures where possible
Stack-based: No recursive algorithms that could cause stack overflow
Extending the Algorithm#
Want to hack on CM-Colors? Here are some areas for improvement:
Alternative Optimization Methods#
Simulated annealing: For even better global optimization
Genetic algorithms: Population-based search
Constrained optimization: Using scipy.optimize for more sophisticated constraints
Different Color Spaces#
LAB: Older but still widely used perceptual space
LUV: Alternative perceptual uniform space
HSV/HSL: More intuitive for designers but less perceptually uniform
Multi-Objective Optimization#
Pareto fronts: Trade-off between multiple objectives
Weighted objectives: Let users specify preference for contrast vs. brand preservation
Color harmony: Maintain color scheme relationships
Testing and Validation#
Our optimization algorithms are validated against:
Brute force reference: Identical results with better performance
Perceptual studies: Human evaluation of “minimal change”
Edge case coverage: Pathological color combinations
Performance benchmarks: Speed comparisons across color ranges
The Math Stuff (For the Really Curious)#
Lagrangian Formulation#
Our constrained optimization problem can be expressed as:
minimize f(L, C) = ΔE(original, new)
subject to: contrast(new, background) ≥ target
ΔE(original, new) ≤ threshold
new ∈ RGB_gamut
Gradient Computation#
For the OKLCH gradient descent, we numerically approximate:
∇f = [∂f/∂L, ∂f/∂C]
Using central differences for stability.
Convergence Criteria#
We stop optimization when:
Objective function change < 1e-6 (numerical convergence)
Valid solution found (early termination)
Maximum iterations reached (50 for gradient descent)
Conclusion#
What looks like “just making colors accessible” is actually a sophisticated optimization problem involving:
Perceptual color science
Numerical optimization
Constrained search algorithms
Color space mathematics
The magic happens in the intersection of human perception, mathematical optimization, and practical engineering constraints. Pretty cool for something that “just fixes your colors,” right?
Still want to go deeper? Check out the research papers on OKLCH color space, Delta E 2000 perceptual difference, and WCAG contrast mathematics. Fair warning: there’s a LOT of math involved. 🤓
Found this interesting or spotted an optimization we could make better? Open an issue - we love talking about color science!