Created
November 4, 2009 00:03
-
-
Save alexch/225603 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # == 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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