Skip to content

Instantly share code, notes, and snippets.

@eymiha
Last active June 29, 2022 21:54
Show Gist options
  • Select an option

  • Save eymiha/c5eb7955ef7f0120f9ae4fd7ec1863f9 to your computer and use it in GitHub Desktop.

Select an option

Save eymiha/c5eb7955ef7f0120f9ae4fd7ec1863f9 to your computer and use it in GitHub Desktop.
Ruby version of Darel Rex Finley's HSP (hue, saturation, perceived brightness) color model. Go to http://alienryderflex.com/hsp.html for his original work.
class WebColor
attr_accessor :r, :g, :b
@@pr, @@pg, @@pb = 0.299, 0.587, 0.114
@@err = 0.0000001
def initialize r = 0.0, g = 0.0, b = 0.0
if r.is_a? Numeric
@r, @g, @b = r, g, b
else
hex = stripHex r
@r = ('0x'+hex[0..1]).to_i(16)/255.0
@g = ('0x'+hex[2..3]).to_i(16)/255.0
@b = ('0x'+hex[4..5]).to_i(16)/255.0
end
end
def stripHex(hex)
/^#?([0-9a-f]{6});?$/.match(hex.downcase)[1] || "000000"
end
def hex
"#%02x%02x%02x" %
[((@r+@@err)*255).to_i, ((@g+@@err)*255).to_i, ((@b+@@err)*255).to_i]
end
def hue
if (@r == @g && @r == @b)
0.0
elsif (@r >= @g && @r >= @b)
if (@b >= @g)
1.0-1.0*(@b-@g)/(@r-@g)/6
else
0.0+1.0*(@g-@b)/(@r-@b)/6
end
elsif (@g >= @r && @g >= @b)
if (@r >= @b)
2.0/6-1.0*(@r-@b)/(@g-@b)/6
else
2.0/6+1.0*(@b-@r)/(@g-@r)/6
end
else # B is largest
if (@g >= @r)
4.0/6-1.0*(@g-@r)/(@b-@r)/6
else
4.0/6+1.0*(@r-@g)/(@g-@g)/6
end
end
end
def saturation
if (@r == @g && @r == @b)
0.0
elsif (@r >= @g && @r >= @b)
if (@b >= @g)
1.0-@g/@r
else
1.0-@b/@r
end
elsif (@g >= @r && @g >= @b)
if (@r >= @b)
1.0-@b/@g
else
1.0-@r/@g
end
else # B is largest
if (@g >= @r)
1.0-@r/@b
else
1.0-@g/@b
end
end
end
def perceived_brightness
Math.sqrt(@@pr*@r*@r + @@pg*@g*@g + @@pb*@b*@b)
end
def WebColor.fromHSP h, s, p
sa = 1.0-s
if sa > 0.0
if h < 1.0/6
h = (h-0.0/6)/6
pa = (1.0-h*(1.0-1.0/sa))
b = p/Math.sqrt(@@pr/(sa*sa)+@@pg*(pa*pa)+@@pb)
r = b/sa
g = b+h*(r-b)
elsif h < 2.0/6
h = (2.0/6-h)*6
pa = (1.0-h*(1.0-1.0/sa))
b = p/Math.sqrt(@@pg/(sa*sa)+@@pr*(pa*pa)+@@pb)
g = b/sa
r = b+h*(g-b)
elsif h < 3.0/6
h = (h-2.0/6)*6
pa = (1.0-h*(1.0-1.0/sa))
r = p/Math.sqrt(@@pg/(sa*sa)+@@pb*(pa*pa)+@@pr)
g = r/sa
b = r+h*(g-r)
elsif h < 4.0/6
h = (4.0/6-h)*6
pa = (1.0-h*(1.0-1.0/sa))
r = p/Math.sqrt(@@pb/(sa*sa)+@@pg*(pa*pa)+@@pr)
b = r/sa
g = r+h*(b-r)
elsif h < 5.0/6
h = (h-4.0/6)*6
pa = (1.0-h*(1.0-1.0/sa))
g = p/Math.sqrt(@@pb/(sa*sa)+@@pr*(pa*pa)+@@pg)
b = g/sa
r = g+h*(b-g)
else
h = (6.0/6-h)*6
pa = (1.0-h*(1.0-1.0/sa))
g = p/Math.sqrt(@@pr/(sa*sa)+@@pb*(pa*pa)+@@pg)
r = g/sa
b = g+h*(r-g)
end
else
if h < 1.0/6
h = (h-0.0/6)*6
r = Math.sqrt(p*p/(@@pr+@@pg*(h*h)))
g = r*h
b = 0.0
elsif h < 2.0/6
h = (2.0/6-h)*6
g = Math.sqrt(p*p/(@@pg+@@pr*(h*h)))
r = g*h
b = 0.0
elsif h < 3.0/6
h = (h-2.0/6)*6
g = Math.sqrt(p*p/(@@pg+@@pb*(h*h)))
b = g*h
r = 0.0
elsif h < 4.0/6
h = (4.0/6-h)*6
b = Math.sqrt(p*p/(@@pb+@@pg*(h*h)))
g = b*h
r = 0.0
elsif h < 5.0/6
h = (h-4.0/6)*6
b = Math.sqrt(p*p/(@@pb+@@pr*(h*h)))
r = b*h
g = 0.0
else
h = (6.0/6-h)*6
r = Math.sqrt(p*p/(@@pr+@@pb*(h*h)))
b = r*h
g = 0.0
end
end
r = 1.0 if r > 1.0
g = 1.0 if g > 1.0
b = 1.0 if b > 1.0
WebColor.new r, g, b
end
end
def test_conversions hex
color = WebColor.new hex
puts "------------"
puts " incoming hex = #{hex}"
puts " converted rgb = (#{color.r}, #{color.g}, #{color.b})"
h = color.hue
s = color.saturation
p = color.perceived_brightness
puts " computed hsp = (#{h}, #{s}, #{p})"
color2 = WebColor.fromHSP h, s, p
puts " reverse rgb = (#{color2.r}, #{color2.g}, #{color2.b})"
hex2 = color2.hex
puts " reverse hex = #{color2.hex} - #{ hex == hex2 ? "passed" : "failed" }"
end
test_conversions "#000000"
test_conversions "#ff0000"
test_conversions "#00ff00"
test_conversions "#0000ff"
test_conversions "#ffff00"
test_conversions "#00ffff"
test_conversions "#ff00ff"
test_conversions "#ffffff"
@acrispino
Copy link

Line 46 has a bug. @g-@g should be @b-@g

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment