color 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/CHANGELOG +4 -0
  2. data/LICENSE +7 -0
  3. data/README +41 -0
  4. data/Rakefile +53 -0
  5. data/lib/color.rb +202 -0
  6. data/test/test_color.rb +131 -0
  7. metadata +50 -0
data/CHANGELOG ADDED
@@ -0,0 +1,4 @@
1
+ === 24 July 2006
2
+ * Initial import of code written 23 July.
3
+ * Adding LICENSE, README, CHANGELOG files.
4
+ * Added Rakefile, Basic Conversion Test for hex.
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2006 Matt Lyon
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,41 @@
1
+ Color
2
+ =====
3
+
4
+ A simple library for dealing with colors. Currently it features:
5
+
6
+ Conversion:
7
+ RGB Int <-> RGB Hex
8
+ HSL <-> RGB
9
+ RGB <-> CMYK
10
+ RGB -> HSV
11
+
12
+ Manipulation:
13
+ Mixing colors
14
+ Adjusting Hue, Saturation, or Lightness values of a color
15
+
16
+ Future plans include #to_web_safe, a palette (fe: Color.new(:black)),
17
+ color wheel traversing ( Color.new(:red).compliment == Color.new(:green) ),
18
+ and possibly color scheme suggestions (analagous, etc).
19
+
20
+ Usage
21
+ =====
22
+
23
+ require 'color'
24
+ => true
25
+ red = Color.new('ff0000')
26
+ => #<Color:0x352fd0 @hue=0.0, @lightness=0.5, @rgb=[255, 0, 0], @saturation=1.0>
27
+ yellow = Color.new('ffff00')
28
+ => #<Color:0x34bb18 @hue=0.166666666666667, @lightness=0.5, @rgb=[255, 255, 0], @saturation=1.0>
29
+ orange = red.mix_with yellow, 0.5
30
+ => #<Color:0x33dea0 @hue=0.0830065359477123, @lightness=0.5, @rgb=[255, 127, 0], @saturation=1.0>
31
+ orange.to_hex
32
+ => "#ff7f00"
33
+ orange.lightness += 0.2
34
+ => 0.7
35
+ orange.to_hex
36
+ => "#ffb266"
37
+
38
+ Warning
39
+ =======
40
+
41
+ This is alpha-quality software. It works according to general poking and prodding, but at this point all of half a morning has been spent on it. The API is subject to change.
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ require 'rake'
2
+ require 'rake/gempackagetask'
3
+ # require 'rake/rdoctask'
4
+ require 'rake/testtask'
5
+ require 'rubygems'
6
+
7
+ $:.unshift(File.dirname(__FILE__) + "/lib")
8
+ require 'color'
9
+
10
+ PKG_NAME = 'color'
11
+ PKG_VERSION = Color::VERSION
12
+ PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
13
+
14
+ RELEASE_NAME = "REL #{PKG_VERSION}"
15
+
16
+ RUBY_FORGE_PROJECT = "color"
17
+ RUBY_FORGE_USER = "mly"
18
+
19
+ desc "Default Task"
20
+ task :default => [ :test ]
21
+
22
+ Rake::TestTask.new do |t|
23
+ t.test_files = FileList['test/*_test.rb']
24
+ t.warning = true
25
+ t.verbose = false
26
+ end
27
+
28
+ spec = Gem::Specification.new do |s|
29
+ s.platform = Gem::Platform::RUBY
30
+ s.name = PKG_NAME
31
+ s.summary = "A Simply Library for Color Manipulation"
32
+ s.description = %q{Manipulate Colors Programatically}
33
+ s.version = PKG_VERSION
34
+
35
+ s.author = "Matt Lyon"
36
+ s.email = "matt@postsomnia.com"
37
+ s.rubyforge_project = RUBY_FORGE_PROJECT
38
+ s.homepage = ""
39
+
40
+ s.has_rdoc = false
41
+ s.require_path = 'lib'
42
+ s.autorequire = 'color'
43
+
44
+ s.files = [ "Rakefile", "README", "CHANGELOG", "LICENSE" ]
45
+ s.files = s.files + Dir.glob("lib/**/*").delete_if {|item| item.include?("\.svn")}
46
+ s.files = s.files + Dir.glob("test/**/*").delete_if {|item| item.include?("\.svn")}
47
+ end
48
+
49
+ Rake::GemPackageTask.new(spec) do |p|
50
+ p.gem_spec = spec
51
+ p.need_tar = true
52
+ p.need_zip = true
53
+ end
data/lib/color.rb ADDED
@@ -0,0 +1,202 @@
1
+ class Color
2
+
3
+ VERSION = "0.1.0"
4
+
5
+ # attr_accessor :rgb
6
+ attr_accessor :hue
7
+ attr_accessor :saturation
8
+ attr_accessor :lightness
9
+
10
+ def initialize(values, mode=:rgb)
11
+ rgb = case mode
12
+ when :hsl
13
+ Color.hsl_to_rgb(values)
14
+ when :rgb
15
+ values = [values].flatten
16
+ case values.size
17
+ when 1
18
+ Color.rgbhex_to_rgb(values.first)
19
+ else
20
+ values
21
+ end
22
+ when :cmyk
23
+ Color.cmyk_to_rgb(values)
24
+ end
25
+ rgb.collect! {|n| n.coerce(0).max.coerce(255).min }
26
+ @hue, @saturation, @lightness = Color.rgb_to_hsl(rgb)
27
+ end
28
+
29
+ def hue=(value)
30
+ value = value.abs >= 1 ? (value/360.0) : value
31
+ @hue = value
32
+ @hue += 1 if @hue < 0 # color is _perceived_ as a wheel, not a continuum
33
+ @hue -= 1 if @hue > 1
34
+ end
35
+
36
+ def hue
37
+ (@hue * 360).round
38
+ end
39
+
40
+ def saturation
41
+ (@saturation * 100).round
42
+ end
43
+
44
+ def saturation=(value)
45
+ @saturation = value.abs >= 1 ? (value / 100.0) : value
46
+ @saturation = @saturation.coerce(0.0).max.coerce(1.0).min
47
+ end
48
+
49
+ def lightness
50
+ (@lightness * 100).round
51
+ end
52
+
53
+ def lightness=(value)
54
+ @lightness = value.abs >= 1 ? (value / 100.0) : value
55
+ @lightness = @lightness.coerce(0.0).max.coerce(1.0).min
56
+ end
57
+
58
+ def mix_with(color, percent_as_decimal=0.5)
59
+ target = color.to_hsl
60
+ deltas = []
61
+ self.hsl.each_with_index {|val, i| deltas[i] = target[i] - val }
62
+ deltas.collect! {|n| n * percent_as_decimal }
63
+ new_color = []
64
+ deltas.each_with_index {|val, i| new_color[i] = val + self.hsl[i] }
65
+ Color.new(new_color, :hsl)
66
+ end
67
+
68
+ def hsl
69
+ [@hue, @saturation, @lightness]
70
+ end
71
+
72
+ def to_hsl
73
+ [(@hue * 360.0).round, (@saturation * 100.0).round, (@lightness * 100.0).round]
74
+ end
75
+
76
+ def to_rgb
77
+ Color.hsl_to_rgb(hsl)
78
+ end
79
+
80
+ def to_hex
81
+ Color.rgb_to_rgbhex(@rgb)
82
+ end
83
+
84
+ def to_cmyk
85
+ Color.rgb_to_cmyk(@rgb)
86
+ end
87
+
88
+ class << self
89
+
90
+ def rgb_to_rgbhex(rgb=[])
91
+ '#' + rgb.collect {|n| "%02X" % n }.join('').downcase
92
+ end
93
+
94
+ def rgbhex_to_rgb(value)
95
+ rg, blue = value.sub(/#/,'').hex.divmod(256)
96
+ red, green = rg.divmod(256)
97
+ return red, green, blue
98
+ end
99
+
100
+ # conversion formulas from http://www.easyrgb.com/math.php
101
+ # They're on Wikipedia too. And elsewhere, really, but the easyrbg ones
102
+ # were most easily translatable to ruby.
103
+ #
104
+ # What's the difference between HSL and HSV? Quite a bit.
105
+ # Wikipedia knows all:
106
+ # http://en.wikipedia.org/wiki/HSV_color_space
107
+ # http://en.wikipedia.org/wiki/HSL_color_space
108
+ #
109
+ # Generally, HSL has a more decoupled notion of saturation and lightness
110
+ # whereas with HSV saturation and value are coupled in such a way as to
111
+ # not behave intuitively. HSV is a bit more mathematically straightforward.
112
+ #
113
+ # Since the author of this library is an artist and has a personal bias
114
+ # towards HSL, this library reflects that bias and uses HSL internally.
115
+ # HSL is also part of the CSS3 Color Module, if I haven't convinced you yet.
116
+
117
+ def rgb_to_hsl(rgb=[])
118
+ r, g, b, min, max, delta = rgb_to_values(rgb)
119
+ lightness = (max + min) / 2.0
120
+ return [0,0,lightness] if delta.zero? # gray, no chroma
121
+ saturation = (lightness < 0.5) ? (delta / (max + min)) : (delta / (2.0 - max - min))
122
+ hue = figure_hue_for(rgb)
123
+ return hue, saturation, lightness
124
+ end
125
+
126
+ def rgb_to_hsl_human(rgb=[])
127
+ h,s,l = Color.rgb_to_hsl(rgb)
128
+ return (h * 360).round, (s * 100).round, (l * 100).round
129
+ end
130
+
131
+ def hsl_to_rgb(hsl=[])
132
+ h, s, l = hsl
133
+ if h > 1 || s > 1 || l > 1
134
+ h = h / 360.0
135
+ s = s * 0.01
136
+ l = l * 0.01
137
+ end
138
+ return [l,l,l].collect {|l| (l * 255.0).round } if s.zero? # gray, no chroma
139
+ v2 = (l < 0.5) ? (l * (1 + s)) : (l + s) - (s * l)
140
+ v1 = 2.0 * l - v2
141
+ rgb = [h+(1/3.0), h, h-(1/3.0)]
142
+ rgb.collect! do |hue|
143
+ hue += 1 if hue < 0
144
+ hue -= 1 if hue > 1
145
+ if (6 * hue) < 1
146
+ v1 + (v2 - v1) * 6 * hue
147
+ elsif (2 * hue) < 1
148
+ v2
149
+ elsif (3 * hue) < 2
150
+ v1 + (v2 - v1) * ((2 / 3.0) - hue) * 6
151
+ else
152
+ v1
153
+ end
154
+ end
155
+ return rgb.collect {|n| (n * 255.0).round }
156
+ end
157
+
158
+ # def rgb_to_hsv(rgb=[])
159
+ # r, g, b, min, max, delta = rgb_to_values(rgb)
160
+ # value = max
161
+ # return [0.0, 0.0, value] if delta.zero? # gray, no chroma
162
+ # saturation = delta / max
163
+ # hue = figure_hue_for(rgb)
164
+ # return hue, saturation, value
165
+ # end
166
+
167
+ def rgb_to_cmyk(rgb=[])
168
+ cmy = rgb.collect {|n| 1 - (n / 255.0)}
169
+ k = cmy.min
170
+ return [0.0,0.0,0.0,1.0] if k == 1.0 # black
171
+ cmy.collect {|n| (n - k) / (1 - k) } << k
172
+ end
173
+
174
+ def cmyk_to_rgb(cmyk=[])
175
+ c,m,y,k = cmyk
176
+ [c,m,y].collect {|n| ((1 - (n * (1 - k) + k)) * 255.0).to_i }
177
+ end
178
+
179
+ private
180
+ def rgb_to_values(rgb=[])
181
+ rgb.collect! {|n| n / 255.0 }
182
+ rgb << rgb.min << rgb.max << rgb.max - rgb.min
183
+ end
184
+
185
+ def figure_hue_for(rgb=[])
186
+ r, g, b, min, max, delta = rgb_to_values(rgb)
187
+ delta_r, delta_g, delta_b = [r,g,b].collect {|n| (((max - n) / 6.0) + (delta / 2.0)) / delta }
188
+ hue = if r.eql? max
189
+ delta_b - delta_g
190
+ elsif g.eql? max
191
+ (1 / 3.0) + delta_r - delta_b
192
+ else # blue
193
+ (2 / 3.0) + delta_g - delta_r
194
+ end
195
+ hue += 1 if hue < 0
196
+ hue -= 1 if hue > 1
197
+ hue
198
+ end
199
+
200
+ end
201
+
202
+ end
@@ -0,0 +1,131 @@
1
+ $:.unshift(File.dirname(__FILE__) + "/../lib/")
2
+
3
+ require 'test/unit'
4
+ require 'color'
5
+
6
+ class TestColor < Test::Unit::TestCase
7
+
8
+ COLORS = {
9
+ :red => {:html => '#ff0000', :rgb => [255,0,0], :hsl => [0,100,50] },
10
+ :yellow => {:html => '#ffff00', :rgb => [255,255,0], :hsl => [60,100,50] },
11
+ :blue => {:html => '#0000ff', :rgb => [0,0,255], :hsl => [240,100,50] },
12
+ :brown => {:html => '#a16328', :rgb => [161,99,40], :hsl => [29,60,39] },
13
+ :carnation => {:html => '#ff5ed0', :rgb => [255,94,208], :hsl => [318,100,68] },
14
+ :cayenne => {:html => '#8d0000', :rgb => [141,0,0], :hsl => [0,100,28] }
15
+ }
16
+
17
+ ## eigenclass conversion methods
18
+
19
+ def test_rgb_to_hex
20
+ COLORS.each_value do |color|
21
+ assert_equal color[:html], Color.rgb_to_rgbhex(color[:rgb])
22
+ end
23
+ end
24
+
25
+ def test_hex_to_rgb
26
+ COLORS.each_value do |color|
27
+ assert_equal color[:rgb], Color.rgbhex_to_rgb(color[:html])
28
+ end
29
+ end
30
+
31
+ def test_rgb_to_hsl
32
+ COLORS.each_value do |color|
33
+ assert_equivalent color[:hsl], Color.rgb_to_hsl_human(color[:rgb])
34
+ end
35
+ end
36
+
37
+ def test_hsl_to_rgb
38
+ COLORS.each_value do |color|
39
+ assert_equivalent color[:rgb], Color.hsl_to_rgb(color[:hsl])
40
+ end
41
+ end
42
+
43
+ def test_hsl_to_rgb_with_floats
44
+ COLORS.each_value do |color|
45
+ assert_equivalent color[:rgb], Color.hsl_to_rgb([(color[:hsl][0]/360.0), (color[:hsl][1]/100.0), (color[:hsl][2]/100.0)])
46
+ end
47
+ end
48
+
49
+ # instance methods
50
+
51
+ def test_intialize_from_hex
52
+ COLORS.each_value do |color|
53
+ instance = Color.new(color[:html])
54
+ assert_equal color[:hsl], instance.to_hsl
55
+ end
56
+ end
57
+
58
+ def test_hue
59
+ assert_equal 60, Color.new(COLORS[:yellow][:html]).hue
60
+ end
61
+
62
+ def test_hue_is
63
+ assert_equal 0.1, Color.new('000000').hue = 0.1
64
+ assert_equal 100, Color.new('000000').hue = 100
65
+ assert_equal 80, Color.new(COLORS[:yellow][:html]).hue += 20
66
+ # TODO: is there a way to have -= return 340 instead of -20 ?
67
+ red = Color.new(COLORS[:red][:html])
68
+ red.hue -= 20
69
+ assert_equal 340, red.hue
70
+ end
71
+
72
+ def test_saturation
73
+ assert_equal 60, Color.new(COLORS[:brown][:html]).saturation
74
+ end
75
+
76
+ def test_saturation_is
77
+ assert_equal 0.1, Color.new('000000').saturation = 0.1
78
+ assert_equal 10, Color.new('000000').saturation = 10
79
+ assert_equal 80, Color.new(COLORS[:red][:html]).saturation -= 20
80
+ assert_equal 70, Color.new(COLORS[:brown][:html]).saturation += 10
81
+ end
82
+
83
+ def test_saturation_is_lower_limit
84
+ brown = Color.new(COLORS[:brown][:html])
85
+ brown.saturation -= 80
86
+ assert_equal 0, brown.saturation
87
+ end
88
+
89
+ def test_saturation_is_upper_limit
90
+ brown = Color.new(COLORS[:brown][:html])
91
+ brown.saturation += 80
92
+ assert_equal 100, brown.saturation
93
+ end
94
+
95
+ def test_lightness
96
+ assert_equal 50, Color.new(COLORS[:red][:html]).lightness
97
+ end
98
+
99
+ def test_lightness_is
100
+ assert_equal 0.1, Color.new('000000').lightness = 0.1
101
+ assert_equal 10, Color.new('000000').lightness = 10
102
+ assert_equal 30, Color.new(COLORS[:red][:html]).lightness -= 20
103
+ assert_equal 70, Color.new(COLORS[:red][:html]).lightness += 20
104
+ end
105
+
106
+ def test_lightness_is_lower_limit
107
+ red = Color.new(COLORS[:red][:html])
108
+ red.lightness -= 80
109
+ assert_equal 0, red.lightness
110
+ end
111
+
112
+ def test_saturation_is_upper_limit
113
+ red = Color.new(COLORS[:red][:html])
114
+ red.lightness += 80
115
+ assert_equal 100, red.lightness
116
+ end
117
+
118
+ def test_mix_with
119
+ red, yellow = Color.new(COLORS[:red][:html]), Color.new(COLORS[:yellow][:html])
120
+ assert_equal 30, red.mix_with(yellow, 0.5).hue
121
+ end
122
+
123
+ #helpers
124
+
125
+ def assert_equivalent(target=[], input=[])
126
+ difference = 0
127
+ target.each_with_index {|value, index| difference += (value - input[index]).abs }
128
+ assert difference <= 5, "[#{input.join(',')}] is not close enough to [#{target.join(',')}]"
129
+ end
130
+
131
+ end
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: color
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2006-08-05 00:00:00 -07:00
8
+ summary: A Simply Library for Color Manipulation
9
+ require_paths:
10
+ - lib
11
+ email: matt@postsomnia.com
12
+ homepage: ""
13
+ rubyforge_project: color
14
+ description: Manipulate Colors Programatically
15
+ autorequire: color
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ authors:
29
+ - Matt Lyon
30
+ files:
31
+ - Rakefile
32
+ - README
33
+ - CHANGELOG
34
+ - LICENSE
35
+ - lib/color.rb
36
+ - test/test_color.rb
37
+ test_files: []
38
+
39
+ rdoc_options: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ executables: []
44
+
45
+ extensions: []
46
+
47
+ requirements: []
48
+
49
+ dependencies: []
50
+