module Colors class HSL < AbstractColor include Helper def initialize(h, s, l) @h, @s, @l = canonicalize(h, s, l) end attr_reader :h, :s, :l def components [h, s, l] end alias hsl_components components def h=(h) @h = Rational(h) % 360 end def s=(s) @s = if s.instance_of?(Integer) check_range(s, 0..255, :s) / 255r else Rational(check_range(s, 0..1, :s)) end end def l=(l) @l = if l.instance_of?(Integer) check_range(l, 0..255, :l) / 255r else Rational(check_range(l, 0..1, :l)) end end alias hue h alias saturation s alias lightness l alias hue= h= alias saturation= s= alias lightness= l= def ==(other) case other when HSLA other == self when HSL h == other.h && s == other.s && l == other.l else super end end def desaturate(factor) HSL.new(h, s*factor, l) end def to_hsl self end def to_hsla(alpha: 1.0) alpha = canonicalize_component(alpha, :alpha) HSLA.new(h, s, l, alpha) end def to_rgb RGB.new(*rgb_components) end def to_rgba(alpha: 1.0) alpha = canonicalize_component(alpha, :alpha) RGBA.new(*rgb_components, alpha) end def rgb_components t2 = if l <= 0.5r l * (s + 1r) else l + s - l * s end t1 = l * 2r - t2 hh = h/60r r = hue_to_rgb(t1, t2, hh + 2) g = hue_to_rgb(t1, t2, hh) b = hue_to_rgb(t1, t2, hh - 2) [r, g, b] end private def hue_to_rgb(t1, t2, h) h += 6r if h < 0 h -= 6r if h >= 6 if h < 1 (t2 - t1) * h + t1 elsif h < 3 t2 elsif h < 4 (t2 - t1) * (4r - h) + t1 else t1 end end private def canonicalize(h, s, l) if [s, l].map(&:class) == [Integer, Integer] canonicalize_from_integer(h, s, l) else [ Rational(h) % 360, canonicalize_component_to_rational(s, :s), canonicalize_component_to_rational(l, :l) ] end end private def canonicalize_from_integer(h, s, l) check_type(s, Integer, :s) check_type(l, Integer, :l) [ Rational(h) % 360, canonicalize_component_from_integer(s, :s), canonicalize_component_from_integer(l, :l) ] end end end