colorable 0.0.8 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,56 +5,42 @@ module Colorable
5
5
  include Enumerable
6
6
  extend Forwardable
7
7
 
8
- def initialize(colorset=nil)
8
+ # Create a X11 colorset object.
9
+ #
10
+ # options::keys should be:
11
+ # +order+:: :NAME(default), :RGB, :HSB or :HEX can be specified.
12
+ # +dir+:: set :+(default) to increment order, :- to decrement order.
13
+ # +colorset+:: set original colorset other than X11 if any.
14
+ def initialize(opt={})
15
+ opt = { order: :name, dir: :+ }.merge(opt)
9
16
  @pos = 0
10
- @colorset =
11
- colorset || COLORNAMES.map { |name, rgb| Colorable::Color.new(name) }
17
+ @colorset = opt[:colorset] ? opt[:colorset] : build_colorset(opt)
12
18
  end
13
19
 
14
20
  def_delegators :@colorset, :size, :first, :last, :to_a
15
21
 
16
- # +Colorset[:order]+ create a ordered colorset by passing a order key.
17
- def self.[](order, dir=:+, colorset=nil)
18
- rgb = [:red, :green, :blue]
19
- hsb = [:hue, :sat, :bright]
20
- blk =
21
- case order
22
- when :red, :blue, :green
23
- ->color{ color.rgb.move_to_top rgb.index(order) }
24
- when :hue, :sat, :bright
25
- ->color{ color.hsb.move_to_top hsb.index(order) }
26
- else
27
- ->color{ color.send order }
28
- end
29
-
30
- case dir
31
- when :+
32
- new(colorset).sort_by(&blk)
33
- when :-
34
- new(colorset).sort_by(&blk).reverse
35
- else
36
- raise ArgumentError, "it must ':+' or ':-'"
37
- end
38
- end
39
-
40
22
  def each(&blk)
41
23
  @colorset.each(&blk)
42
24
  end
43
25
 
26
+ # Returns a top color object in colorset
44
27
  def at(pos=0)
45
28
  @colorset[(@pos+pos)%size]
46
29
  end
47
30
 
31
+ # Returns a next color object in colorset
48
32
  def next(n=1)
49
33
  @pos = (@pos+n)%size
50
34
  at
51
35
  end
52
36
 
37
+ # Returns a previous color object in colorset
53
38
  def prev(n=1)
54
39
  @pos = (@pos-n)%size
55
40
  at
56
41
  end
57
42
 
43
+ # Rewind a cursor to the top
58
44
  def rewind
59
45
  @pos = 0
60
46
  at
@@ -65,17 +51,60 @@ module Colorable
65
51
  (@pos+idx)%size if idx
66
52
  end
67
53
 
54
+ # Returns a sorted colorset defined by passed block
68
55
  def sort_by(&blk)
69
- self.class.new @colorset.sort_by(&blk)
56
+ self.class.new colorset: @colorset.sort_by(&blk)
70
57
  end
71
58
 
59
+ # Returns a reversed colorset
72
60
  def reverse
73
- self.class.new @colorset.reverse
61
+ self.class.new colorset: @colorset.reverse
74
62
  end
75
63
 
76
64
  def to_s
77
- "#<%s %d/%d pos='%s:%s'>" % [:Colorset, @pos, size, at.name, at]
65
+ "#<%s %d/%d pos='%s/%s/%s'>" % [:Colorset, @pos, size, at.name, at.rgb, at.hsb]
78
66
  end
79
67
  alias :inspect :to_s
68
+
69
+ private
70
+ def build_colorset(opt)
71
+ rgb_part = [:red, :green, :blue]
72
+ hsb_part = [:hue, :sat, :bright]
73
+
74
+ order = opt[:order].downcase.intern
75
+
76
+ mode =
77
+ case order
78
+ when :name then :NAME
79
+ when :rgb, *rgb_part then :RGB
80
+ when :hsb, :hsv, *hsb_part then :HSB
81
+ else
82
+ raise ArgumentError, "Invalid order option given"
83
+ end
84
+
85
+ colorset = COLORNAMES.map do |name, _|
86
+ Colorable::Color.new(name).tap {|c| c.mode = mode }
87
+ end
88
+
89
+ order_cond =
90
+ case order
91
+ when *rgb_part
92
+ ->color{ color.rgb.move_to_top rgb_part.index(order) }
93
+ when *hsb_part
94
+ ->color{ color.hsb.move_to_top hsb_part.index(order) }
95
+ when :name, :rgb, :hsb, :hsv
96
+ ->color{ color.send order }
97
+ end
98
+
99
+ case opt[:dir].intern
100
+ when :+
101
+ colorset.sort_by(&order_cond)
102
+ when :-
103
+ colorset.sort_by(&order_cond).reverse
104
+ else
105
+ raise ArgumentError, "Invalid dir option given"
106
+ end
107
+ end
108
+
80
109
  end
81
110
  end
@@ -1,20 +1,15 @@
1
1
  module Colorable
2
2
  module Converter
3
- class NoNameError < StandardError; end
4
-
5
3
  def name2rgb(name)
6
- COLORNAMES.assoc(name)[1]
7
- rescue
8
- raise NoNameError, "'#{name}' not exist"
4
+ COLORNAMES.assoc(name).tap{|c, rgb| break rgb if c }
9
5
  end
10
6
 
11
7
  def rgb2name(rgb)
12
- validate_rgb(rgb)
13
- COLORNAMES.rassoc(rgb).tap { |c, _| break c if c }
8
+ COLORNAMES.rassoc(rgb)
9
+ .tap { |c, _| break c if c }
14
10
  end
15
11
 
16
12
  def rgb2hsb(rgb)
17
- validate_rgb(rgb)
18
13
  r, g, b = rgb.map(&:to_f)
19
14
  hue = Math.atan2(Math.sqrt(3)*(g-b), 2*r-g-b).to_degree
20
15
 
@@ -28,7 +23,6 @@ module Colorable
28
23
 
29
24
  class NotImplemented < StandardError; end
30
25
  def rgb2hsl(rgb)
31
- validate_rgb(rgb)
32
26
  raise NotImplemented, 'Not Implemented Yet'
33
27
  r, g, b = rgb.map(&:to_f)
34
28
  hue = Math.atan2(Math.sqrt(3)*(g-b), 2*r-g-b).to_degree
@@ -41,7 +35,6 @@ module Colorable
41
35
  end
42
36
 
43
37
  def hsb2rgb(hsb)
44
- validate_hsb(hsb)
45
38
  hue, sat, bright = hsb
46
39
  norm = ->range{ hue.norm(range, 0..255) }
47
40
  rgb_h =
@@ -51,7 +44,7 @@ module Colorable
51
44
  when 120..180 then [0, 255, norm[120..180]]
52
45
  when 180..240 then [0, 255-norm[180..240], 255]
53
46
  when 240..300 then [norm[240..300], 0, 255]
54
- when 300..360 then [255, 0, 255-norm[300.360]]
47
+ when 300..360 then [255, 0, 255-norm[300..360]]
55
48
  end
56
49
  rgb_s = rgb_h.map { |val| val + (255-val) * (1-sat/100.0) }
57
50
  rgb_s.map { |val| (val * bright/100.0).round }
@@ -59,7 +52,6 @@ module Colorable
59
52
  alias :hsv2rgb :hsb2rgb
60
53
 
61
54
  def rgb2hex(rgb)
62
- validate_rgb(rgb)
63
55
  hex = rgb.map do |val|
64
56
  val.to_s(16).tap { |h| break "0#{h}" if h.size==1 }
65
57
  end
@@ -67,35 +59,8 @@ module Colorable
67
59
  end
68
60
 
69
61
  def hex2rgb(hex)
70
- validate_hex(hex)
71
62
  _, *hex = hex.unpack('A1A2A2A2')
72
63
  hex.map { |val| val.to_i(16) }
73
64
  end
74
-
75
- private
76
- def validate_rgb(rgb)
77
- if rgb.all? { |val| val.between?(0, 255) }
78
- rgb
79
- else
80
- raise ArgumentError, "'#{rgb}' is invalid for a RGB value"
81
- end
82
- end
83
-
84
- def validate_hsb(hsb)
85
- h, *sb = hsb
86
- if h.between?(0, 360) && sb.all? { |val| val.between?(0, 100) }
87
- hsb
88
- else
89
- raise ArgumentError, "'#{hsb}' is invalid for a HSB value"
90
- end
91
- end
92
-
93
- def validate_hex(hex)
94
- if hex.match(/^#[0-9A-F]{6}$/i)
95
- hex.upcase
96
- else
97
- raise ArgumentError, "'#{hex}' is invalid for a HEX value"
98
- end
99
- end
100
65
  end
101
66
  end
@@ -16,9 +16,11 @@ class Array
16
16
  def same?(&blk)
17
17
  self.uniq(&blk).size==1
18
18
  end
19
+ end
19
20
 
20
- def move_to_top(idx)
21
- arr = self.dup
22
- arr.insert 0, arr.delete_at(idx)
23
- end
21
+ class Pattern < Array
22
+ def ===(other)
23
+ zip(other).all? { |a, b| a === b }
24
+ end
24
25
  end
26
+
@@ -1,3 +1,3 @@
1
1
  module Colorable
2
- VERSION = "0.0.8"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -0,0 +1,356 @@
1
+ require_relative 'spec_helper'
2
+
3
+ include Colorable
4
+ describe RGB do
5
+ describe ".new" do
6
+ context "without arguments" do
7
+ subject { RGB.new }
8
+ its(:to_a) { should eql [0, 0, 0] }
9
+ its(:rgb) { should eql [0, 0, 0] }
10
+ its(:red) { should eql 0 }
11
+ its(:green) { should eql 0 }
12
+ its(:blue) { should eql 0 }
13
+ end
14
+
15
+ context "with arguments" do
16
+ subject { RGB.new 100, 255, 255 }
17
+ its(:to_a) { should eql [100, 255, 255] }
18
+ its(:rgb) { should eql [100, 255, 255] }
19
+ its(:red) { should eql 100 }
20
+ its(:green) { should eql 255 }
21
+ its(:blue) { should eql 255 }
22
+ end
23
+
24
+ context "pass out of RGB range" do
25
+ it "raise ArgumentError" do
26
+ expect { RGB.new(0, 0, 256) }.to raise_error ArgumentError
27
+ expect { RGB.new(-10, 0, 255) }.to raise_error ArgumentError
28
+ expect { RGB.new(256, 256, 256) }.to raise_error ArgumentError
29
+ end
30
+ end
31
+ end
32
+
33
+ describe "#+" do
34
+ context "pass an array of numbers" do
35
+ before(:all) { @rgb = RGB.new(100, 100, 100) }
36
+ subject { @rgb + [0, 50, 100] }
37
+ its(:rgb) { should eql [100, 150, 200] }
38
+ it "keep original same" do @rgb.rgb.should eql [100, 100, 100] end
39
+ end
40
+
41
+ context "when '+' makes out of RGB range or rack of numbers" do
42
+ it "raise ArgumentError" do
43
+ expect { RGB.new(100, 100, 100) + [0, 50, 200] }.to raise_error ArgumentError
44
+ expect { RGB.new(100, 100, 100) + [0, -150, 0] }.to raise_error ArgumentError
45
+ expect { RGB.new(100, 100, 100) + [0, 150] }.to raise_error ArgumentError
46
+ end
47
+ end
48
+
49
+ context "pass a Fixnum" do
50
+ before(:all) { @rgb = RGB.new(100, 100, 100) }
51
+ it { (@rgb + 50).rgb.should eql [150, 150, 150] }
52
+ it "raise ArgumentError" do
53
+ expect { @rgb + 160 }.to raise_error ArgumentError
54
+ end
55
+ end
56
+
57
+ context "coerce make Fixnum#+ accept rgb object" do
58
+ it { (10 + RGB.new(100, 100, 100)).rgb.should eql [110, 110, 110] }
59
+ end
60
+ end
61
+
62
+ describe "#-" do
63
+ context "pass an array of numbers" do
64
+ before(:all) { @rgb = RGB.new(100, 100, 100) }
65
+ subject { @rgb - [0, 50, 100] }
66
+ its(:rgb) { should eql [100, 50, 0] }
67
+ it "keep original same" do @rgb.rgb.should eql [100, 100, 100] end
68
+ end
69
+
70
+ context "when '-' makes out of RGB range" do
71
+ it "raise ArgumentError" do
72
+ expect { RGB.new(100, 100, 100) - [0, 50, 200] }.to raise_error ArgumentError
73
+ expect { RGB.new(100, 100, 100) - [0, -250, 0] }.to raise_error ArgumentError
74
+ end
75
+ end
76
+
77
+ context "pass a Fixnum" do
78
+ before(:all) { @rgb = RGB.new(100, 100, 100) }
79
+ it { (@rgb - 50).rgb.should eql [50, 50, 50] }
80
+ it "raise ArgumentError" do
81
+ expect { @rgb - 160 }.to raise_error ArgumentError
82
+ end
83
+ end
84
+ end
85
+
86
+ describe "#to_s" do
87
+ it { RGB.new(0, 50, 100).to_s.should eql "rgb(0,50,100)"}
88
+ end
89
+
90
+ describe "#to_name" do
91
+ it { RGB.new(240, 248, 255).to_name.should eql 'Alice Blue' }
92
+ end
93
+
94
+ describe "#to_hsb" do
95
+ it { RGB.new(240, 248, 255).to_hsb.should eql [208, 6, 100] }
96
+ end
97
+
98
+ describe "#to_hex" do
99
+ it { RGB.new(240, 248, 255).to_hex.should eql '#F0F8FF' }
100
+ end
101
+ end
102
+
103
+ describe HSB do
104
+ describe ".new" do
105
+ context "without arguments" do
106
+ subject { HSB.new }
107
+ its(:to_a) { should eql [0, 0, 0] }
108
+ its(:hsb) { should eql [0, 0, 0] }
109
+ its(:hue) { should eql 0 }
110
+ its(:sat) { should eql 0 }
111
+ its(:bright) { should eql 0 }
112
+ end
113
+
114
+ context "with arguments" do
115
+ subject { HSB.new 300, 70, 90 }
116
+ its(:to_a) { should eql [300, 70, 90] }
117
+ its(:hsb) { should eql [300, 70, 90] }
118
+ its(:hue) { should eql 300 }
119
+ its(:sat) { should eql 70 }
120
+ its(:bright) { should eql 90 }
121
+ end
122
+
123
+ context "pass out of HSB range" do
124
+ it "raise ArgumentError" do
125
+ expect { HSB.new(0, 0, 120) }.to raise_error ArgumentError
126
+ expect { HSB.new(360, 0, 80) }.to raise_error ArgumentError
127
+ expect { HSB.new(0, -10, 0) }.to raise_error ArgumentError
128
+ end
129
+ end
130
+ end
131
+
132
+ describe "#+" do
133
+ context "pass an array of numbers" do
134
+ before(:all) { @hsb = HSB.new(300, 70, 90) }
135
+ subject { @hsb + [0, 5, 10] }
136
+ its(:hsb) { should eql [300, 75, 100] }
137
+ it "keep original same" do @hsb.hsb.should eql [300, 70, 90] end
138
+ end
139
+
140
+ context "when '+' makes out of HSB range or rack of numbers" do
141
+ it "raise ArgumentError" do
142
+ expect { HSB.new(300, 70, 90) + [0, 50, 0] }.to raise_error ArgumentError
143
+ expect { HSB.new(300, 70, 90) + [0, 0, -100] }.to raise_error ArgumentError
144
+ expect { HSB.new(300, 70, 90) + [0, 5] }.to raise_error ArgumentError
145
+ expect { HSB.new(300, 70, 90) + 5 }.to raise_error ArgumentError
146
+ end
147
+ end
148
+ end
149
+
150
+ describe "#-" do
151
+ context "pass an array of numbers" do
152
+ before(:all) { @hsb = HSB.new(300, 70, 90) }
153
+ subject { @hsb - [0, 5, 10] }
154
+ its(:hsb) { should eql [300, 65, 80] }
155
+ it "keep original same" do @hsb.hsb.should eql [300, 70, 90] end
156
+ end
157
+
158
+ context "when '-' makes out of HSB range" do
159
+ it "raise ArgumentError" do
160
+ expect { HSB.new(300, 7, 90) - [305, 0, 10] }.to raise_error ArgumentError
161
+ expect { HSB.new(300, 70, 90) - [0, -35, 0] }.to raise_error ArgumentError
162
+ end
163
+ end
164
+ end
165
+
166
+ describe "#to_s" do
167
+ it { HSB.new(0, 50, 100).to_s.should eql "hsb(0,50,100)"}
168
+ end
169
+
170
+ describe "#to_name" do
171
+ it { HSB.new(208, 6, 100).to_name.should eql 'Alice Blue' }
172
+ end
173
+
174
+ describe "#to_rgb" do
175
+ it { HSB.new(208, 6, 100).to_rgb.should eql [240, 248, 255] }
176
+ end
177
+
178
+ describe "#to_hex" do
179
+ it { HSB.new(208, 6, 100).to_hex.should eql '#F0F8FF' }
180
+ end
181
+ end
182
+
183
+ describe NAME do
184
+ describe ".new" do
185
+ context "with valid name" do
186
+ subject { NAME.new :alice_blue }
187
+ its(:to_s) { should eql 'Alice Blue' }
188
+ its(:name) { should eql 'Alice Blue' }
189
+ its(:sym) { should eql :alice_blue }
190
+ end
191
+
192
+ context "with invalid name" do
193
+ it "raise ArgumentError" do
194
+ expect { NAME.new :abc_color }.to raise_error ArgumentError
195
+ end
196
+ end
197
+ end
198
+
199
+ describe "dark?" do
200
+ it { NAME.new(:navy).dark?.should be_true }
201
+ it { NAME.new(:alice_blue).dark?.should be_false }
202
+ end
203
+
204
+ describe "#+" do
205
+ context "pass an irregal argument" do
206
+ it "raise ArgumentError" do
207
+ expect { NAME.new(:alice_blue) + [0, 150, 100] }.to raise_error ArgumentError
208
+ end
209
+ end
210
+
211
+ context "pass a Fixnum" do
212
+ before(:all) { @name = NAME.new(:alice_blue) }
213
+ it { (@name + 1).name.should eql 'Antique White' }
214
+ it { (@name + 2).name.should eql 'Aqua' }
215
+ end
216
+
217
+ context "coerce make Fixnum#+ accept name object" do
218
+ it { (1 + NAME.new(:alice_blue)).to_s.should eql 'Antique White' }
219
+ end
220
+ end
221
+
222
+ describe "#-" do
223
+ context "pass an irregal argument" do
224
+ it "raise ArgumentError" do
225
+ expect { NAME.new(:alice_blue) - [0, 150, 100] }.to raise_error ArgumentError
226
+ end
227
+ end
228
+
229
+ context "pass a Fixnum" do
230
+ before(:all) { @name = NAME.new(:alice_blue) }
231
+ it { (@name - 1).name.should eql 'Yellow Green' }
232
+ it { (@name - 2).name.should eql 'Yellow' }
233
+ end
234
+
235
+ context "coerce make Fixnum#+ accept name object" do
236
+ it { (1 - NAME.new(:alice_blue)).to_s.should eql 'Yellow Green' }
237
+ end
238
+ end
239
+
240
+ describe "#to_rgb" do
241
+ it { NAME.new('Alice Blue').to_rgb.should eql [240, 248, 255] }
242
+ end
243
+
244
+ describe "#to_hsb" do
245
+ it { NAME.new('Alice Blue').to_hsb.should eql [208, 6, 100] }
246
+ end
247
+
248
+ describe "#to_hex" do
249
+ it { NAME.new('Alice Blue').to_hex.should eql '#F0F8FF' }
250
+ end
251
+ end
252
+
253
+ describe HEX do
254
+ describe ".new" do
255
+ context "without arguments" do
256
+ subject { HEX.new }
257
+ its(:to_s) { should eql '#FFFFFF' }
258
+ its(:hex) { should eql '#FFFFFF' }
259
+ its(:to_a) { should eql ['FF', 'FF', 'FF'] }
260
+ end
261
+
262
+ context "with arguments" do
263
+ it { HEX.new('#FFFF00').to_s.should eql '#FFFF00' }
264
+ it { HEX.new('#ffff00').to_s.should eql '#FFFF00' }
265
+ it { HEX.new('FFFF00').to_s.should eql '#FFFF00' }
266
+ it { HEX.new('#ff0').to_s.should eql '#FFFF00' }
267
+ it { HEX.new(['FF', 'FF', '00']).to_s.should eql '#FFFF00' }
268
+ end
269
+
270
+ context "pass out of HEX range" do
271
+ it "raise ArgumentError" do
272
+ expect { HEX.new('#FFGG00') }.to raise_error ArgumentError
273
+ expect { HEX.new('aaxxxx') }.to raise_error ArgumentError
274
+ expect { HEX.new(['FF', 'FF', 00]) }.to raise_error ArgumentError
275
+ end
276
+ end
277
+ end
278
+
279
+ describe "#+" do
280
+ context "pass a HEX string" do
281
+ before(:all) { @hex = HEX.new('#FF0000') }
282
+ it { (@hex + '#00FFCC').hex.should eql '#FFFFCC'}
283
+ it { (@hex + '#00ffcc').hex.should eql '#FFFFCC'}
284
+ it { (@hex + '00FFCC').hex.should eql '#FFFFCC'}
285
+ it { (@hex + '#0FC').hex.should eql '#FFFFCC'}
286
+ it "keep original" do @hex.hex.should eql '#FF0000' end
287
+ end
288
+
289
+ context "out of HEX range" do
290
+ it "raise ArgumentError" do
291
+ expect { HEX.new('#FF0000') + '#010000' }.to raise_error ArgumentError
292
+ expect { HEX.new('#FF0000') + '#00ffgg' }.to raise_error ArgumentError
293
+ expect { HEX.new('#000000') + [0, 50, 100] }.to raise_error ArgumentError
294
+ end
295
+ end
296
+
297
+ context "pass a Fixnum" do
298
+ before(:all) { @hex = HEX.new('#000000') }
299
+ it { (@hex + 255).hex.should eql '#FFFFFF' }
300
+ it "raise ArgumentError" do
301
+ expect { @hex + 260 }.to raise_error ArgumentError
302
+ end
303
+ end
304
+
305
+ context "coerce make Fixnum#+ accept hex object" do
306
+ it { (255 + HEX.new('#000000')).hex.should eql '#FFFFFF' }
307
+ end
308
+ end
309
+
310
+ describe "#-" do
311
+ context "pass a HEX string" do
312
+ before(:all) { @hex = HEX.new('#00FFFF') }
313
+ it { (@hex - '#00FFCC').hex.should eql '#000033'}
314
+ it { (@hex - '#00ffcc').hex.should eql '#000033'}
315
+ it { (@hex - '00FFCC').hex.should eql '#000033'}
316
+ it { (@hex - '#0FC').hex.should eql '#000033'}
317
+ it "keep original" do @hex.hex.should eql '#00FFFF' end
318
+ end
319
+
320
+ context "out of HEX range" do
321
+ it "raise ArgumentError" do
322
+ expect { HEX.new('#FF0000') - '#000100' }.to raise_error ArgumentError
323
+ expect { HEX.new('#FF0000') - '#0000cc' }.to raise_error ArgumentError
324
+ expect { HEX.new('#000000') - [0, 50, 100] }.to raise_error ArgumentError
325
+ end
326
+ end
327
+
328
+ context "pass a Fixnum" do
329
+ before(:all) { @hex = HEX.new('#FFFFFF') }
330
+ it { (@hex - 255).hex.should eql '#000000' }
331
+ it "raise ArgumentError" do
332
+ expect { @hex + 260 }.to raise_error ArgumentError
333
+ end
334
+ end
335
+
336
+ context "coerce make Fixnum#+ accept hex object" do
337
+ it { (255 - HEX.new('#FFFFFF')).hex.should eql '#000000' }
338
+ end
339
+ end
340
+
341
+ describe "#to_s" do
342
+ it { HEX.new('#F0F8FF').to_s.should eql '#F0F8FF' }
343
+ end
344
+
345
+ describe "#to_rgb" do
346
+ it { HEX.new('#F0F8FF').to_rgb.should eql [240, 248, 255] }
347
+ end
348
+
349
+ describe "#to_hsb" do
350
+ it { HEX.new('#F0F8FF').to_hsb.should eql [208, 6, 100] }
351
+ end
352
+
353
+ describe "#to_name" do
354
+ it { HEX.new('#F0F8FF').to_name.should eql 'Alice Blue' }
355
+ end
356
+ end