lib/mittsu/math/color.rb
require 'mittsu/math/vector3'
module Mittsu
class Color < Vector3
ELEMENTS = { r: 0, g: 1, b: 2 }
DIMENSIONS = ELEMENTS.count
def initialize(*args)
super(0, 0, 0)
case args.length
when 3 then self.set_rgb(*args)
when 1 then self.set(args.first)
when 0 then self.set_rgb(1.0, 1.0, 1.0)
else raise ArgumentError, "Arguments must be (r, g, b), (color), or none"
end
end
alias :r :x
alias :g :y
alias :b :z
alias :r= :x=
alias :g= :y=
alias :b= :z=
def set(value)
case value
when Color
self.copy(value)
when Integer
self.set_hex(value)
when String
self.set_style(value)
else
raise ArgumentError, "Arguments must be Color, Integer or String"
end
self
end
def set_hex(hex)
hex = hex.floor
self.r = (hex >> 16 & 255) / 255.0
self.g = (hex >> 8 & 255) / 255.0
self.b = (hex & 255) / 255.0
self
end
def set_rgb(r, g, b)
@elements = [r.to_f, g.to_f, b.to_f]
self
end
def set_hsl(h, s, l)
# h,s,l ranges are in 0.0 - 1.0
if s.zero?
self.r = self.g = self.b = l
else
p = l <= 0.5 ? l * (1.0 + s) : l + s - (l * s)
q = (2.0 * l) - p
self.r = hue2rgb(q, p, h + 1.0 / 3.0)
self.g = hue2rgb(q, p, h)
self.b = hue2rgb(q, p, h - 1.0 / 3.0)
end
self
end
def set_style(style)
# rgb(255,0,0)
if /^rgb\((\d+), ?(\d+), ?(\d+)\)$/i =~ style
self.r = [255.0, $1.to_f].min / 255.0
self.g = [255.0, $2.to_f].min / 255.0
self.b = [255.0, $3.to_f].min / 255.0
return self
end
# rgb(100%,0%,0%)
if /^rgb\((\d+)\%, ?(\d+)\%, ?(\d+)\%\)$/i =~ style
self.r = [100.0, $1.to_f].min / 100.0
self.g = [100.0, $2.to_f].min / 100.0
self.b = [100.0, $3.to_f].min / 100.0
return self
end
# #ff0000
if /^\#([0-9a-f]{6})$/i =~ style
self.set_hex($1.hex)
return self
end
# #f00
if /^\#([0-9a-f])([0-9a-f])([0-9a-f])$/i =~ style
self.set_hex(($1 + $1 + $2 + $2 + $3 + $3).hex)
return self
end
# red
if /^(\w+)$/i =~ style
self.set_hex(Mittsu::ColorKeywords[style])
return self
end
end
def copy_gamma_to_linear(color, gamma_factor = 2.0)
self.r = color.r ** gamma_factor
self.g = color.g ** gamma_factor
self.b = color.b ** gamma_factor
self
end
def copy_linear_to_gamma(color, gamma_factor = 2.0)
safe_inverse = (gamma_factor > 0) ? (1.0 / gamma_factor) : 1.0
self.r = color.r ** safe_inverse
self.g = color.g ** safe_inverse
self.b = color.b ** safe_inverse
self
end
def convert_gamma_to_linear
rr, gg, bb = self.r, self.g, self.b
self.r = rr * rr
self.g = gg * gg
self.b = bb * bb
self
end
def convert_linear_to_gamma
self.r = ::Math.sqrt(self.r)
self.g = ::Math.sqrt(self.g)
self.b = ::Math.sqrt(self.b)
self
end
def hex
(self.r * 255).to_i << 16 ^ (self.g * 255).to_i << 8 ^ (self.b * 255).to_i << 0
end
def hex_string
('000000' + self.hex.to_s(16))[-6..-1]
end
def hsl(target = nil)
# h,s,l ranges are in 0.0 - 1.0
hsl = target || { h: 0.0, s: 0.0, l: 0.0 }
rr, gg, bb = self.r, self.g, self.b
max = [r, g, b].max
min = [r, g, b].min
hue, saturation = nil, nil
lightness = (min + max) / 2.0
if min == max
hue = 0.0
saturation = 0.0
else
delta = max - min
saturation = lightness <= 0.5 ? delta / (max + min) : delta / (2.0 - max - min)
case max
when rr then hue = (gg - bb) / delta + (gg < bb ? 6.0 : 0.0)
when gg then hue = (bb - rr) / delta + 2.0
when bb then hue = (rr - gg) / delta + 4.0
end
hue /= 6.0
end
hsl[:h] = hue
hsl[:s] = saturation
hsl[:l] = lightness
hsl
end
def style
"rgb(#{ (self.r * 255).to_i },#{ (self.g * 255).to_i },#{ (self.b * 255).to_i })"
end
def offset_hsl(h, s, l)
hsl = self.hsl
hsl[:h] += h
hsl[:s] += s
hsl[:l] += l
self.set_hsl(hsl[:h], hsl[:s], hsl[:l])
self
end
alias :add_colors :add_vectors
private
def hue2rgb(p, q, t)
t += 1.0 if t < 0.0
t -= 1.0 if t > 1.0
return p + (q - p) * 6.0 * t if t < 1.0 / 6.0
return q if t < 1.0 / 2.0
return p + (q - p) * 6.0 * (2.0 / 3.0 - t) if t < 2.0 / 3.0
p
end
end
end