abachrome
How do I map out-of-gamut colors to a valid range in Ruby?
Wide-gamut color spaces like OKLCH can describe colors that lie outside what sRGB monitors can display. Abachrome provides gamut objects for sRGB, Display P3, and Rec.2020 that clip or map those coordinates to the nearest in-gamut value.
Checking Gamut Membership
require 'abachrome'
# A vivid OKLCH color that exceeds sRGB chroma limits
vivid = Abachrome.from_oklch(0.7, 0.35, 145)
gamut = Abachrome::Gamut::SRGB.new
puts gamut.in_gamut?(vivid.coordinates) # => false
Mapping to sRGB
vivid = Abachrome.from_oklch(0.7, 0.35, 145)
gamut = Abachrome::Gamut::SRGB.new
mapped = gamut.map(vivid.coordinates)
puts Abachrome::Outputs::CSS.format(Abachrome.from_srgb(*mapped))
Targeting Other Gamuts
p3_gamut = Abachrome::Gamut::P3.new
rec2020_gamut = Abachrome::Gamut::Rec2020.new
# Map to Display P3 (wider than sRGB but narrower than Rec.2020)
p3_safe = p3_gamut.map(vivid.coordinates)
Practical Use: Processing User Input
When your application accepts arbitrary CSS color input, gamut-map before rendering to avoid invalid CSS values:
def safe_css(input)
color = Abachrome.parse(input).to_srgb
gamut = Abachrome::Gamut::SRGB.new
coords = gamut.map(color.coordinates)
Abachrome::Outputs::CSS.format(Abachrome.from_srgb(*coords))
end
puts safe_css('oklch(0.8 0.4 140)') # vivid green → clamped hex
Notes
- Gamut mapping reduces chroma (colorfulness) rather than clipping individual channels, preserving the color’s hue and lightness as much as possible.
- If you only need to clamp and don’t care about perceptual accuracy, simple
coordinates.map { |v| v.clamp(0, 1) }on sRGB coordinates is sufficient.