Skip to content

Instantly share code, notes, and snippets.

@alexch
Created November 4, 2009 00:03
Show Gist options
  • Save alexch/225603 to your computer and use it in GitHub Desktop.
Save alexch/225603 to your computer and use it in GitHub Desktop.
# == Schema Information
# Schema version: 10
#
# Table name: descriptions
#
# id :integer(4) not null, primary key
# type :string(255) not null
# person_id :integer(4) not null
# mood_id :integer(4) not null
# value :integer(4) not null
# use_consensus :boolean(1) not null
# created_at :datetime
# updated_at :datetime
#
# the value is an integer in RGB space
class Color < Description
NEUTRAL = "#888888"
def self.to_rgb(val)
val.nil? ? NEUTRAL : "#%.6x" % val
end
def self.consensus(mood)
all = mood.colors
if all.empty?
return nil
else
# all = Color.find(:all, :conditions => {:mood_id => mood.id})
hue_sum = saturation_sum = lightness_sum = 0
all.each_with_index do |color,i|
hue_sum += color.hue
saturation_sum += color.saturation
lightness_sum += color.lightness
end
n = all.size
from_hsl [hue_sum/n,saturation_sum/n,lightness_sum/n]
end
end
def self.from_hsl(hsl_array)
rgb_array = hsl_to_rgb(hsl_array)
new(:value =>
(rgb_array[0] * 0xFF).round * 0x010000 +
(rgb_array[1] * 0xFF).round * 0x000100 +
(rgb_array[2] * 0xFF).round
)
end
def initialize(options = {})
super(options)
self.rgb = options[:rgb] if options[:rgb]
end
def min
0
end
def max
0xffffff
end
def rgb=(s)
self.value = s.gsub(/^\#/, "0x").to_i(16)
end
def rgb
Color.to_rgb(value)
end
def red
(value / 0x10000) & 0xff
end
def green
(value / 0x100) & 0xff
end
def blue
(value) & 0xff
end
def hue
hsl[0]
end
def saturation
hsl[1]
end
def lightness
hsl[2]
end
def contrast
lightness < 0.5 ? "#ffffff" : '#000000'
end
# Various color utility functions stolen from Farbtastic and converted from JavaScript
# Farbtastic was written by Steven Wittens and is licensed under the GPL.
def hsl
r = red/255.0
g = green/255.0
b = blue/255.0
min = [r,g,b].min
max = [r,g,b].max
delta = max - min;
l = (min + max) / 2;
s = 0;
if (l > 0 && l < 1)
s = delta / (l < 0.5 ? (2 * l) : (2 - 2 * l));
end
h = 0;
if (delta > 0)
if (max == r && max != g)
h += (g - b) / delta;
end
if (max == g && max != b)
h += (2 + (b - r) / delta);
end
if (max == b && max != r)
h += (4 + (r - g) / delta);
end
h /= 6;
end
[h, s, l];
end
def self.hsl_to_rgb(hsl)
h = hsl[0]
s = hsl[1]
l = hsl[2]
m2 = (l <= 0.5) ? l * (s + 1) : l + s - l*s;
m1 = l * 2 - m2;
return [
hue_to_rgb(m1, m2, h+0.33333),
hue_to_rgb(m1, m2, h),
hue_to_rgb(m1, m2, h-0.33333)
]
end
def self.hue_to_rgb(m1, m2, h)
h = (h < 0) ? h + 1 : ((h > 1) ? h - 1 : h);
if (h * 6 < 1)
return m1 + (m2 - m1) * h * 6;
end
if (h * 2 < 1)
return m2;
end
if (h * 3 < 2)
return m1 + (m2 - m1) * (0.66666 - h) * 6;
end
return m1;
end
NONE = Color.new(:rgb => NEUTRAL)
end
require File.dirname(__FILE__) + '/../test_helper'
class ColorTest < MoodlogTestCase
fixtures :people, :moods
def setup
@palette = [
@white = Color.new(:value => 0xffffff),
@black = Color.new(:value => 0x000000),
@red = Color.new(:value => 0xff0000),
@green = Color.new(:value => 0x00ff00),
@blue = Color.new(:value => 0x0000ff),
@fuchsia = Color.new(:value => 0xff00ff),
@purple = Color.new(:value => 0x800080),
@turquoise = Color.new(:value => 0x48D1CC),
Color.new(:value => 0x112233),
Color.new(:value => 0x001100),
Color.new(:value => 0x000011),
Color.new(:value => 0x000001),
]
end
def test_new_with_rgb
color = Color.new(:rgb => "#ffffff")
assert_equal 0xffffff, color.value
end
def test_create_with_rgb
color = create_color(:rgb => "#ffffff")
assert_equal 0xffffff, color.value
end
def test_create_with_rgb_case_insensitive
color = create_color(:rgb => "#fFFfFF")
assert_equal 0xffffff, color.value
end
def test_update_with_rgb
color = create_color(:rgb => "#ffffff")
color.update_attributes(:rgb => "#000011")
assert_equal 0x000011, color.value
end
def test_get_rgb
["#ffffff", "#112233", "#001100", "#000011", "#000001", "#000000"].each do |s|
color = create_color(:rgb => s)
assert_equal s, color.rgb
end
end
def test_to_rgb
assert_equal "#dababe", Color.to_rgb(0xdababe)
assert_equal Color::NEUTRAL, Color.to_rgb(nil)
end
def test_hsl_for_black
black = create_color(:rgb => "#000000")
assert_equal 0, black.hue
assert_equal 0, black.saturation
assert_equal 0, black.lightness
end
def test_hsl_for_white
white = create_color(:rgb => "#FFFFFF")
assert_equal 0, white.hue
assert_equal 0, white.saturation
assert_equal 1, white.lightness
end
def test_hsl_to_rgb
random_colors.each do |color|
# assert_between_0_and_1 color.hue # todo: figure out why this is possible
assert_between_0_and_1 color.saturation
assert_between_0_and_1 color.lightness
assert_equal color.rgb, Color.from_hsl(color.hsl).rgb
end
end
def assert_between_0_and_1(x)
assert x >= 0, "#{x} should be >= 0"
assert x <= 1.0 || approx_equal?(x, 1.0), "#{x} should be <= 1.0"
end
def approx_equal?(a,b,threshold = 0.0000001)
(a-b).abs<threshold
end
def random_colors
rgbs = @palette
50.times { rgbs << Color.new(:rgb => "#%.6x" % rand(0x1000000).to_i) }
rgbs
end
def test_contrast
assert_equal "#ffffff", create_color(:rgb => "#000000").contrast
assert_equal "#000000", create_color(:rgb => "#ffffff").contrast
assert_equal "#000000", create_color(:rgb => "#888888").contrast
assert_equal "#ffffff", create_color(:rgb => "#777777").contrast
random_colors.each do |color|
assert_equal color.lightness < 0.5 ? '#ffffff' : '#000000', color.contrast, color.rgb
end
end
def test_consensus_where_none
assert_nil Color.consensus(moods(:happy))
end
def test_consensus_where_one
create_color(:person => people(:quentin), :mood => moods(:happy), :rgb => "#FF0000")
assert_equal "#ff0000", Color.consensus(moods(:happy)).rgb
end
def test_parts
assert_equal 0xff, @red.red
assert_equal 0, @green.red
assert_equal 0, @blue.red
assert_equal 0xff, @fuchsia.red
assert_equal 0x00, @red.green
assert_equal 0xff, @green.green
assert_equal 0x00, @blue.green
assert_equal 0x00, @fuchsia.green
end
# todo: use HSL instead of RGB to find average
# def test_consensus_where_many
# mood = moods(:happy)
# red_sum = green_sum = blue_sum = 0
# @palette.each_with_index do |x,i|
# person = Person.create!(:username => "person#{i}",
# :password => 'test', :password_confirmation => 'test',
# :addresses => {0 => {:email => "person#{i}@example.com"}})
# color = create_color(:person => person, :mood => mood, :rgb => x.rgb)
# red_sum += color.red
# green_sum += color.green
# blue_sum += color.blue
# end
# consensus = Color.consensus(mood)
# n = @palette.size
# assert_equal red_sum/n, consensus.red
# assert_equal blue_sum/n, consensus.blue
# assert_equal green_sum/n, consensus.green
# end
#
def test_consensus_where_many
mood = moods(:happy)
hue_sum = saturation_sum = lightness_sum = 0
@palette.each_with_index do |x,i|
person = Person.create!(:username => "person#{i}",
:password => 'test', :password_confirmation => 'test',
:addresses => {0 => {:email => "person#{i}@example.com"}})
color = create_color(:person => person, :mood => mood, :rgb => x.rgb)
hue_sum += color.hue
saturation_sum += color.saturation
lightness_sum += color.lightness
end
consensus = Color.consensus(mood)
n = @palette.size
assert_nearly_equal hue_sum/n, consensus.hue
assert_nearly_equal lightness_sum/n, consensus.lightness
assert_nearly_equal saturation_sum/n, consensus.saturation
end
def assert_nearly_equal(expected, actual)
assert_equal((expected*10.0).round, (actual*10.0).round, "Expected #{expected} and #{actual} to be nearly equal")
end
def create_color(options = {})
options = {:person => people(:quentin), :mood => moods(:happy)}.merge(options)
Color.create(options)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment