colorable 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +123 -93
- data/lib/colorable.rb +6 -2
- data/lib/colorable/color.rb +87 -24
- data/lib/colorable/color_space.rb +250 -205
- data/lib/colorable/colorset.rb +4 -4
- data/lib/colorable/version.rb +1 -1
- data/spec/color_space_spec.rb +99 -27
- data/spec/color_spec.rb +124 -40
- data/spec/colorable_spec.rb +63 -49
- data/spec/colorset_spec.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a4005fd6c6a2f7c11c4331bfc96f906c06bce741
|
4
|
+
data.tar.gz: 9af36345b9fa55dbaeb639e7c494c25ebd042ece
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b6645965c7f1971d803610d21763cbd120f517d2cc2b782a3c4e10e3d8fc64e19f2e95a2bc126288dfbb2a362fb0ac620216bc603f0c743f978ba691491c4004
|
7
|
+
data.tar.gz: 969e6b784f5529f579eb9f0364378be285bff790ca81c2b47e45fe8bdc413a83a33fd5452139953c92ad08966987610927c910024ea0fbad4f023fbb360f7bbb
|
data/README.md
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
# Colorable
|
2
2
|
|
3
|
-
Colorable is a color handler written in Ruby which
|
4
|
-
|
3
|
+
Colorable is a color handler written in Ruby, which has following functionalities;
|
4
|
+
|
5
|
+
1. Color conversion: convertible between X11 colorname, HEX, RGB and HSB values.
|
6
|
+
2. Color composition: a color object can be composible using math operators.
|
7
|
+
3. Color enumeration: a color object can be enumerable within X11 colors.
|
8
|
+
4. Color mode: a color object has a mode which represent output state of the color.
|
5
9
|
|
6
10
|
## Installation
|
7
11
|
|
@@ -19,119 +23,145 @@ Or install it yourself as:
|
|
19
23
|
|
20
24
|
## Usage
|
21
25
|
|
26
|
+
|
22
27
|
Create a color object:
|
23
28
|
|
24
29
|
require "colorable"
|
25
30
|
include Colorable
|
26
31
|
|
27
|
-
#
|
28
|
-
|
29
|
-
c.to_s #=> "Lime Green"
|
30
|
-
c.rgb.to_a #=> [50, 205, 50]
|
31
|
-
c.hsb.to_a #=> [120, 76, 80]
|
32
|
-
c.hex.to_s #=> "#32CD32"
|
33
|
-
c.dark? #=> false
|
34
|
-
|
35
|
-
# with Array of RGB values
|
36
|
-
c = Color.new [50, 205, 50]
|
37
|
-
c.to_s #=> "rgb(50,205,50)"
|
38
|
-
c.name.to_s #=> "Lime Green"
|
39
|
-
c.hsb.to_a #=> [120, 76, 80]
|
40
|
-
c.hex.to_s #=> "#32CD32"
|
41
|
-
|
42
|
-
# with a HEX string
|
43
|
-
c = Color.new '#32CD32'
|
44
|
-
c.to_s #=> "#32CD32"
|
45
|
-
|
46
|
-
# with a RGB, HSB or HEX object
|
47
|
-
c = Color.new RGB.new(50, 205, 50)
|
48
|
-
c = Color.new HSB.new(120, 76, 80)
|
49
|
-
c = Color.new HEX.new('#32CD32')
|
50
|
-
|
51
|
-
Manipulate color object:
|
52
|
-
|
53
|
-
c = Color.new :lime_green
|
54
|
-
|
55
|
-
c.to_s #=> "Lime Green"
|
56
|
-
c.rgb.to_a #=> [50, 205, 50]
|
57
|
-
c.hsb.to_a #=> [120, 76, 80]
|
58
|
-
c.hex.to_s #=> "#32CD32"
|
59
|
-
c.dark? #=> false
|
60
|
-
|
61
|
-
# info returns information of the color
|
62
|
-
c.info #=> {:NAME=>"Lime Green", :RGB=>[50, 205, 50], :HSB=>[120, 76, 80], :HEX=>"#32CD32", :MODE=>:NAME, :DARK=>false}
|
63
|
-
|
64
|
-
# next, prev returns next, prev color object in X11 color sequence
|
65
|
-
c.next.to_s #=> "Linen"
|
66
|
-
c.next(2).to_s #=> "Magenta"
|
67
|
-
c.prev.to_s #=> "Lime"
|
68
|
-
c.prev(2).to_s #=> "Light Yellow"
|
69
|
-
|
70
|
-
# +, - returns incremented or decremented color object
|
71
|
-
(c + 1).to_s #=> "Linen"
|
72
|
-
(c + 2).to_s #=> "Magenta"
|
73
|
-
(c - 1).to_s #=> "Lime"
|
74
|
-
(c - 2).to_s #=> "Light Yellow"
|
75
|
-
|
76
|
-
Color object has a mode which represent output mode of the color. Behaviours of `#to_s`, `next`, `prev`, `+`, `-` will be changed based on the mode. You can change the mode with `#mode=` between :NAME, :RGB, :HSB, :HEX.
|
77
|
-
|
78
|
-
c = Color.new 'Lime Green'
|
79
|
-
c.mode = :NAME
|
80
|
-
c.to_s #=> "Lime Green"
|
81
|
-
c.next.to_s #=> "Linen"
|
32
|
+
# from a X11 colorname
|
33
|
+
Color.new 'Alice Blue' # => #<Colorable::Color 'Alice Blue<rgb(240,248,255)/hsb(208,6,100)/#F0F8FF>'>
|
82
34
|
|
83
|
-
|
84
|
-
|
85
|
-
c.next.to_s #=> "rgb(60,179,113)"
|
86
|
-
c.next.name.to_s #=> "Medium Sea Green"
|
87
|
-
(c + 10).to_s #=> "rgb(60,215,60)"
|
88
|
-
(c + [0, 50, 100]).to_s #=> "rgb(50, 255, 150)"
|
35
|
+
# from a HEX string
|
36
|
+
Color.new '#F0F8FF' # => #<Colorable::Color 'Alice Blue<rgb(240,248,255)/hsb(208,6,100)/#F0F8FF>'>
|
89
37
|
|
90
|
-
|
38
|
+
# from RGB values
|
39
|
+
Color.new [240, 248, 255] # => #<Colorable::Color 'Alice Blue<rgb(240,248,255)/hsb(208,6,100)/#F0F8FF>'>
|
91
40
|
|
92
|
-
|
93
|
-
|
41
|
+
# from a HSB object
|
42
|
+
Color.new HSB.new(208, 6, 100) # => #<Colorable::Color 'Alice Blue<rgb(240,248,255)/hsb(208,6,100)/#F0F8FF>'>
|
94
43
|
|
95
|
-
|
96
|
-
c.class #=> Colorable::Color
|
44
|
+
# using #to_color methods
|
97
45
|
|
98
|
-
|
99
|
-
c.class #=> Colorable::Color
|
46
|
+
'Alice Blue'.to_color # => #<Colorable::Color 'Alice Blue<rgb(240,248,255)/hsb(208,6,100)/#F0F8FF>'>
|
100
47
|
|
101
|
-
|
102
|
-
c.class #=> Colorable::Color
|
48
|
+
:alice_blue.to_color # => #<Colorable::Color 'Alice Blue<rgb(240,248,255)/hsb(208,6,100)/#F0F8FF>'>
|
103
49
|
|
104
|
-
|
50
|
+
'#f0f8ff'.to_color # => #<Colorable::Color 'Alice Blue<rgb(240,248,255)/hsb(208,6,100)/#F0F8FF>'>
|
105
51
|
|
106
|
-
|
52
|
+
[240, 248, 255].to_color # => #<Colorable::Color 'Alice Blue<rgb(240,248,255)/hsb(208,6,100)/#F0F8FF>'>
|
53
|
+
|
54
|
+
0xf0f8ff.to_color # => #<Colorable::Color 'Alice Blue<rgb(240,248,255)/hsb(208,6,100)/#F0F8FF>'>
|
55
|
+
|
56
|
+
|
57
|
+
Color conversion:
|
58
|
+
|
59
|
+
c = Color.new :alice_blue
|
60
|
+
|
61
|
+
c.name # => "Alice Blue"
|
62
|
+
c.rgb # => [240, 248, 255]
|
63
|
+
c.hsb # => [208, 6, 100]
|
64
|
+
c.hex # => "#F0F8FF"
|
65
|
+
c.dark? # => false
|
66
|
+
c.info # => {:name=>"Alice Blue", :rgb=>[240, 248, 255], :hsb=>[208, 6, 100], :hex=>"#F0F8FF", :mode=>:NAME, :dark=>false}
|
67
|
+
|
68
|
+
[240, 248, 255].to_color.hex # => "#F0F8FF"
|
69
|
+
[240, 248, 255].to_color.hsb # => [208, 6, 100]
|
70
|
+
|
71
|
+
|
72
|
+
Color composition:
|
73
|
+
|
74
|
+
red = Color.new :red
|
75
|
+
green = Color.new :green
|
76
|
+
blue = Color.new :blue
|
77
|
+
|
78
|
+
yellow = red + green
|
79
|
+
yellow.info # => {:name=>"Yellow", :rgb=>[255, 255, 0], :hsb=>[60, 100, 100], :hex=>"#FFFF00", :mode=>:NAME, :dark=>false}
|
80
|
+
|
81
|
+
red + blue # => #<Colorable::Color 'Fuchsia<rgb(255,0,255)/hsb(300,100,100)/#FF00FF>'>
|
82
|
+
|
83
|
+
green + blue # => #<Colorable::Color 'Aqua<rgb(0,255,255)/hsb(180,100,100)/#00FFFF>'>
|
84
|
+
|
85
|
+
red + green + blue # => #<Colorable::Color 'White<rgb(255,255,255)/hsb(0,0,100)/#FFFFFF>'>
|
107
86
|
|
108
|
-
|
87
|
+
red - green # => #<Colorable::Color 'Black<rgb(0,0,0)/hsb(0,0,0)/#000000>'>
|
88
|
+
red * green # => #<Colorable::Color 'Black<rgb(0,0,0)/hsb(0,0,0)/#000000>'>
|
89
|
+
red / green # => #<Colorable::Color 'Yellow<rgb(255,255,0)/hsb(60,100,100)/#FFFF00>'>
|
90
|
+
|
91
|
+
Color enumeration:
|
92
|
+
|
93
|
+
c = Color.new :alice_blue
|
94
|
+
|
95
|
+
c.next # => #<Colorable::Color 'Antique White<rgb(250,235,215)/hsb(35,14,98)/#FAEBD7>'>
|
96
|
+
c.next(10) # => #<Colorable::Color 'Blue Violet<rgb(138,43,226)/hsb(271,81,89)/#8A2BE2>'>
|
97
|
+
|
98
|
+
c.prev # => #<Colorable::Color 'Yellow Green<rgb(154,205,50)/hsb(79,76,80)/#9ACD32>'>
|
99
|
+
c.prev(10) # => #<Colorable::Color 'Teal<rgb(0,128,128)/hsb(180,100,50)/#008080>'>
|
100
|
+
|
101
|
+
c + 1 # => #<Colorable::Color 'Antique White<rgb(250,235,215)/hsb(35,14,98)/#FAEBD7>'>
|
102
|
+
c + 10 # => #<Colorable::Color 'Blue Violet<rgb(138,43,226)/hsb(271,81,89)/#8A2BE2>'>
|
103
|
+
c - 1 # => #<Colorable::Color 'Yellow Green<rgb(154,205,50)/hsb(79,76,80)/#9ACD32>'>
|
104
|
+
c - 10 # => #<Colorable::Color 'Teal<rgb(0,128,128)/hsb(180,100,50)/#008080>'>
|
105
|
+
|
106
|
+
10.times.map { c = c.next }.map(&:name) # => ["Antique White", "Aqua", "Aquamarine", "Azure", "Beige", "Bisque", "Black", "Blanched Almond", "Blue", "Blue Violet"]
|
107
|
+
|
108
|
+
|
109
|
+
Color mode:
|
110
|
+
|
111
|
+
c = Color.new :alice_blue
|
112
|
+
c.mode # => :NAME
|
113
|
+
c.to_s # => "Alice Blue"
|
114
|
+
c.next # => #<Colorable::Color 'Antique White<rgb(250,235,215)/hsb(35,14,98)/#FAEBD7>'>
|
115
|
+
c + 1 # => #<Colorable::Color 'Antique White<rgb(250,235,215)/hsb(35,14,98)/#FAEBD7>'>
|
116
|
+
|
117
|
+
c.mode = :RGB
|
118
|
+
c.to_s # => "rgb(240,248,255)"
|
119
|
+
c.next # => #<Colorable::Color 'Honeydew<rgb(240,255,240)/hsb(120,6,100)/#F0FFF0>'>
|
120
|
+
c + [15, -20, -74] # => #<Colorable::Color 'Moccasin<rgb(255,228,181)/hsb(39,29,100)/#FFE4B5>'>
|
121
|
+
c - 20 # => #<Colorable::Color '<rgb(220,228,235)/hsb(208,6,92)/#DCE4EB>'>
|
122
|
+
|
123
|
+
c.mode = :HSB # !> `*' interpreted as argument prefix
|
124
|
+
c.to_s # => "hsb(208,6,100)"
|
125
|
+
c.next # => #<Colorable::Color 'Slate Gray<rgb(112,128,144)/hsb(210,22,56)/#708090>'>
|
126
|
+
c + [152, 94, 0] # => #<Colorable::Color 'Red<rgb(255,0,0)/hsb(0,100,100)/#FF0000>'>
|
127
|
+
|
128
|
+
c.mode = :HEX
|
129
|
+
c.to_s # => "#F0F8FF"
|
130
|
+
c.next # => #<Colorable::Color 'Honeydew<rgb(240,255,240)/hsb(120,6,100)/#F0FFF0>'>
|
131
|
+
c + 4 # => #<Colorable::Color '<rgb(244,252,3)/hsb(62,99,99)/#F4FC03>'>
|
132
|
+
|
133
|
+
Create a X11 Colorset object
|
134
|
+
|
135
|
+
cs = Colorset.new # => #<Colorable::Colorset 0/144 pos='Alice Blue<rgb(240,248,255)/hsb(208,6,100)>'>
|
136
|
+
|
137
|
+
# with option
|
138
|
+
cs = Colorset.new(order: :RGB) # => #<Colorable::Colorset 0/144 pos='Black<rgb(0,0,0)/hsb(0,0,0)>'>
|
139
|
+
cs = Colorset.new(order: :HSB, dir: :-) # => #<Colorable::Colorset 0/144 pos='Light Pink<rgb(255,182,193)/hsb(352,29,100)>'>
|
109
140
|
|
110
|
-
# with option
|
111
|
-
cs = Colorset.new(order: :RGB) #=> #<Colorset 0/144 pos='Black/rgb(0,0,0)/hsb(0,0,0)'>
|
112
|
-
cs = Colorset.new(order: :HSB, dir: :-) #=> #<Colorset 0/144 pos='Light Pink/rgb(255,182,193)/hsb(352,29,100)'>
|
113
141
|
|
114
142
|
Manupilate colorset:
|
115
143
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
144
|
+
cs = Colorset.new
|
145
|
+
cs.size # => 144
|
146
|
+
cs.at # => #<Colorable::Color 'Alice Blue<rgb(240,248,255)/hsb(208,6,100)/#F0F8FF>'>
|
147
|
+
cs.at.to_s # => "Alice Blue"
|
148
|
+
cs.at(1).to_s # => "Antique White"
|
149
|
+
cs.at(2).to_s # => "Aqua"
|
150
|
+
|
151
|
+
# next(prev) methods moves cursor position
|
152
|
+
cs.next.to_s # => "Antique White"
|
153
|
+
cs.at.to_s # => "Antique White"
|
154
|
+
cs.next.to_s # => "Aqua"
|
155
|
+
cs.at.to_s # => "Aqua"
|
156
|
+
cs.rewind
|
157
|
+
cs.at.to_s # => "Alice Blue"
|
121
158
|
|
122
|
-
|
123
|
-
cs.next.to_s #=> "Antique White"
|
124
|
-
cs.at.to_s #=> "Antique White"
|
125
|
-
cs.next.to_s #=> "Aqua"
|
126
|
-
cs.at.to_s #=> "Aqua"
|
127
|
-
cs.rewind
|
128
|
-
cs.at.to_s #=> "Alice Blue"
|
159
|
+
cs.map(&:to_s).take(10) # => ["Alice Blue", "Antique White", "Aqua", "Aquamarine", "Azure", "Beige", "Bisque", "Black", "Blanched Almond", "Blue"]
|
129
160
|
|
130
|
-
|
161
|
+
cs.sort_by(&:rgb).take(10).map(&:rgb) # => [[0, 0, 0], [0, 0, 128], [0, 0, 139], [0, 0, 205], [0, 0, 255], [0, 100, 0], [0, 128, 0], [0, 128, 128], [0, 139, 139], [0, 191, 255]]
|
131
162
|
|
132
|
-
|
163
|
+
cs.sort_by(&:hsb).take(10).map(&:hsb) # => [[0, 0, 0], [0, 0, 41], [0, 0, 50], [0, 0, 66], [0, 0, 75], [0, 0, 75], [0, 0, 83], [0, 0, 86], [0, 0, 96], [0, 0, 100]]
|
133
164
|
|
134
|
-
cs.sort_by(&:hsb).map(&:to_s).take(10) #=> ["Black", "Dim Gray", "Gray2", "Dark Gray", "Gray", "Silver", "Light Gray", "Gainsboro", "White Smoke", "White"]
|
135
165
|
|
136
166
|
|
137
167
|
## Contributing
|
data/lib/colorable.rb
CHANGED
@@ -12,6 +12,10 @@ module Colorable
|
|
12
12
|
|
13
13
|
end
|
14
14
|
|
15
|
-
[String, Symbol, Array].each do |klass|
|
16
|
-
|
15
|
+
[String, Symbol, Array, Fixnum].each do |klass|
|
16
|
+
klass.class_eval do
|
17
|
+
define_method(:to_color) do
|
18
|
+
Colorable::Color.new (klass==Fixnum ? self.to_s(16) : self)
|
19
|
+
end
|
20
|
+
end
|
17
21
|
end
|
data/lib/colorable/color.rb
CHANGED
@@ -2,7 +2,6 @@ module Colorable
|
|
2
2
|
class Color
|
3
3
|
class NameError < StandardError; end
|
4
4
|
include Comparable
|
5
|
-
attr_reader :name, :rgb
|
6
5
|
|
7
6
|
# Create a Color object which has several representations of a color.
|
8
7
|
#
|
@@ -24,7 +23,7 @@ module Colorable
|
|
24
23
|
|
25
24
|
# Set output mode.
|
26
25
|
def mode=(mode)
|
27
|
-
modes = [
|
26
|
+
modes = [_rgb, _hsb, _name, _hex]
|
28
27
|
@mode = modes.detect { |m| m.class.to_s.match /#{mode}/i } || begin
|
29
28
|
raise ArgumentError, "Invalid mode given"
|
30
29
|
end
|
@@ -34,37 +33,65 @@ module Colorable
|
|
34
33
|
@mode.to_s
|
35
34
|
end
|
36
35
|
|
36
|
+
def inspect
|
37
|
+
"#<%s '%s<%s/%s/%s>'>" % [self.class, _name, _rgb, _hsb, _hex]
|
38
|
+
end
|
39
|
+
|
37
40
|
# Returns information of the color object
|
38
41
|
def info
|
39
42
|
{
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
43
|
+
name: name,
|
44
|
+
rgb: rgb,
|
45
|
+
hsb: hsb,
|
46
|
+
hex: hex,
|
47
|
+
mode: mode,
|
48
|
+
dark: dark?
|
46
49
|
}
|
47
50
|
end
|
48
51
|
|
52
|
+
def _name
|
53
|
+
@name
|
54
|
+
end
|
55
|
+
|
56
|
+
def _rgb
|
57
|
+
@rgb
|
58
|
+
end
|
59
|
+
|
60
|
+
def _hex
|
61
|
+
@hex ||= HEX.new _rgb.to_hex
|
62
|
+
end
|
63
|
+
|
64
|
+
def _hsb
|
65
|
+
@hsb ||= HSB.new *_rgb.to_hsb
|
66
|
+
end
|
67
|
+
|
68
|
+
def name
|
69
|
+
_name.to_s
|
70
|
+
end
|
71
|
+
|
49
72
|
def hex
|
50
|
-
|
73
|
+
_hex.to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
def rgb
|
77
|
+
_rgb.to_a
|
51
78
|
end
|
52
79
|
|
53
80
|
def hsb
|
54
|
-
|
81
|
+
_hsb.to_a
|
55
82
|
end
|
56
83
|
alias :hsv :hsb
|
57
84
|
|
58
85
|
%w(red green blue).each do |c|
|
59
|
-
define_method(c) {
|
86
|
+
define_method(c) { _rgb.send c }
|
60
87
|
end
|
61
88
|
|
62
89
|
%w(hue sat bright).each do |c|
|
63
|
-
define_method(c) {
|
90
|
+
define_method(c) { _hsb.send c }
|
64
91
|
end
|
65
92
|
|
66
93
|
def <=>(other)
|
67
|
-
if [self.name, other.name].any?(&:
|
94
|
+
if [self.name, other.name].any?(&:empty?)
|
68
95
|
self.rgb <=> other.rgb
|
69
96
|
else
|
70
97
|
self.name <=> other.name
|
@@ -79,6 +106,7 @@ module Colorable
|
|
79
106
|
idx = @@colorset[mode].find_index(self)
|
80
107
|
@@colorset[mode].at(idx+n).tap{|c| c.mode = mode } if idx
|
81
108
|
end
|
109
|
+
alias :succ :next
|
82
110
|
|
83
111
|
# Returns a previous color object in X11 colors.
|
84
112
|
# The color sequence is determined by its color mode.
|
@@ -87,21 +115,51 @@ module Colorable
|
|
87
115
|
end
|
88
116
|
|
89
117
|
def dark?
|
90
|
-
!!DARK_COLORS.detect { |d| d == name
|
118
|
+
!!DARK_COLORS.detect { |d| d == self.name }
|
91
119
|
end
|
92
120
|
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
|
97
|
-
|
121
|
+
# Color addition
|
122
|
+
#
|
123
|
+
# +other+ can be:
|
124
|
+
# Color object: apply minimum compositing with its RGBs.
|
125
|
+
# Array of values or Fixnum: addiction applies based on its color mode.
|
126
|
+
def +(other)
|
127
|
+
case other
|
128
|
+
when Color
|
129
|
+
new_by_composed_rgb(:+, other)
|
130
|
+
else
|
131
|
+
self.class.new @mode + other
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Color subtruction
|
136
|
+
#
|
137
|
+
# +other+ can be:
|
138
|
+
# Color object: apply maximum compositing with its RGBs.
|
139
|
+
# Array of values or Fixnum: subtruction applies based on its color mode.
|
140
|
+
def -(other)
|
141
|
+
case other
|
142
|
+
when Color
|
143
|
+
new_by_composed_rgb(:-, other)
|
144
|
+
else
|
145
|
+
self.class.new @mode - other
|
146
|
+
end
|
98
147
|
end
|
99
148
|
|
100
|
-
#
|
101
|
-
#
|
102
|
-
#
|
103
|
-
|
104
|
-
|
149
|
+
# Color multiplication
|
150
|
+
#
|
151
|
+
# +other+ should be a Color object.
|
152
|
+
# It applies multiply compositing with its RGBs.
|
153
|
+
def *(other)
|
154
|
+
new_by_composed_rgb(:*, other)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Color division
|
158
|
+
#
|
159
|
+
# +other+ should be a Color object.
|
160
|
+
# It applies screen compositing with its RGBs.
|
161
|
+
def /(other)
|
162
|
+
new_by_composed_rgb(:/, other)
|
105
163
|
end
|
106
164
|
|
107
165
|
private
|
@@ -163,5 +221,10 @@ module Colorable
|
|
163
221
|
raise NameError, "Invalid color name given" unless res.name
|
164
222
|
end
|
165
223
|
end
|
224
|
+
|
225
|
+
def new_by_composed_rgb(op, other)
|
226
|
+
rgb = self._rgb.send(op, other._rgb)
|
227
|
+
self.class.new(rgb).tap { |c| c.mode = self.mode }
|
228
|
+
end
|
166
229
|
end
|
167
230
|
end
|
@@ -1,36 +1,36 @@
|
|
1
1
|
module Colorable
|
2
|
-
|
2
|
+
class ColorSpace
|
3
3
|
include Colorable::Converter
|
4
|
-
|
4
|
+
include Comparable
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
def <=>(other)
|
7
|
+
self.to_a <=> other.to_a
|
8
|
+
end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
def move_to_top(idx)
|
11
|
+
arr = self.to_a
|
12
|
+
arr.insert 0, arr.delete_at(idx)
|
13
|
+
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
def +(arg)
|
16
|
+
raise "Subclass must implement it"
|
17
|
+
end
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
def -(arg)
|
20
|
+
arg = arg.is_a?(Fixnum) ? -arg : arg.map(&:-@)
|
21
|
+
self + arg
|
22
|
+
end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
def coerce(arg)
|
25
|
+
[self, arg]
|
26
|
+
end
|
27
27
|
|
28
|
-
|
29
|
-
|
28
|
+
def to_s
|
29
|
+
name = "#{self.class}"[/\w+$/].downcase
|
30
30
|
"#{name}(%i,%i,%i)" % to_a
|
31
|
-
|
31
|
+
end
|
32
32
|
|
33
|
-
|
33
|
+
private
|
34
34
|
def validate(pattern, data)
|
35
35
|
case Array(data)
|
36
36
|
when Pattern[*Array(pattern)] then data
|
@@ -38,138 +38,183 @@ module Colorable
|
|
38
38
|
raise ArgumentError, "'#{data}' is invalid for a #{self.class} value"
|
39
39
|
end
|
40
40
|
end
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
41
|
+
end
|
42
|
+
|
43
|
+
class RGB < ColorSpace
|
44
|
+
attr_accessor :rgb, :r, :g, :b
|
45
|
+
def initialize(r=0,g=0,b=0)
|
46
|
+
@r, @g, @b = @rgb = validate_rgb([r, g, b])
|
47
|
+
end
|
48
|
+
alias :red :r
|
49
|
+
alias :green :g
|
50
|
+
alias :blue :b
|
51
|
+
alias :to_a :rgb
|
52
|
+
|
53
|
+
# Color addition
|
54
|
+
#
|
55
|
+
# +other+ can be:
|
56
|
+
# RGB object: apply minimum compositing.
|
57
|
+
# Array of RGB values: each values added to each of RGBs.
|
58
|
+
# Fixnum: other number added to all of RGBs.
|
59
|
+
def +(other)
|
60
|
+
rgb =
|
61
|
+
case other
|
62
|
+
when RGB
|
63
|
+
compound_by(other.rgb) { |a, b| [a+b, 255].min }
|
64
|
+
when Array
|
65
|
+
raise ArgumentError, "Invalid size of Array given" unless other.size==3
|
66
|
+
compound_by(other) { |a, b| (a + b) % 256 }
|
67
|
+
when Fixnum
|
68
|
+
compound_by([other] * 3) { |a, b| (a + b) % 256 }
|
69
|
+
else
|
70
|
+
raise ArgumentError, "Invalid argument given"
|
71
|
+
end
|
72
|
+
self.class.new *rgb
|
73
|
+
end
|
74
|
+
|
75
|
+
# Color subtruction
|
76
|
+
#
|
77
|
+
# +other+ can be:
|
78
|
+
# RGB object: apply maximum compositing.
|
79
|
+
# Array of RGB values: each values added to each of RGBs.
|
80
|
+
# Fixnum: other number added to all of RGBs.
|
81
|
+
def -(other)
|
82
|
+
case other
|
83
|
+
when RGB
|
84
|
+
rgb = compound_by(other.rgb) { |a, b| [a+b-255, 0].max }
|
85
|
+
self.class.new *rgb
|
86
|
+
else
|
87
|
+
super
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Color multiplication
|
92
|
+
#
|
93
|
+
# +other+ should be a Color object.
|
94
|
+
# It applies multiply compositing.
|
95
|
+
def *(other)
|
96
|
+
raise ArgumentError, "Invalid argument given" unless other.is_a?(RGB)
|
97
|
+
rgb = compound_by(other.rgb) { |a, b| [(a*b/255.0).round, 0].max }
|
98
|
+
self.class.new *rgb
|
99
|
+
end
|
100
|
+
|
101
|
+
# Color division
|
102
|
+
#
|
103
|
+
# +other+ should be a Color object.
|
104
|
+
# It applies screen compositing.
|
105
|
+
def /(other)
|
106
|
+
raise ArgumentError, "Invalid argument given" unless other.is_a?(RGB)
|
107
|
+
rgb = compound_by(other.rgb) { |a, b| [a+b-(a*b/255.0).round, 255].min }
|
108
|
+
self.class.new *rgb
|
109
|
+
end
|
110
|
+
|
111
|
+
def to_name
|
112
|
+
rgb2name(self.to_a)
|
113
|
+
end
|
114
|
+
|
115
|
+
def to_hsb
|
116
|
+
rgb2hsb(self.to_a)
|
117
|
+
end
|
118
|
+
|
119
|
+
def to_hex
|
120
|
+
rgb2hex(self.to_a)
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
83
124
|
def validate_rgb(rgb)
|
84
125
|
validate([0..255, 0..255, 0..255], rgb)
|
85
126
|
end
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
+
|
128
|
+
def compound_by(rgb, &blk)
|
129
|
+
self.rgb.zip(rgb).map(&blk)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class HSB < ColorSpace
|
134
|
+
attr_accessor :hsb, :h, :s, :b
|
135
|
+
def initialize(h=0,s=0,b=0)
|
136
|
+
@h, @s, @b = @hsb = validate_hsb([h, s, b])
|
137
|
+
end
|
138
|
+
alias :hue :h
|
139
|
+
alias :sat :s
|
140
|
+
alias :bright :b
|
141
|
+
alias :to_a :hsb
|
142
|
+
undef :coerce
|
143
|
+
|
144
|
+
# Pass Array of [h, s, b] or a Fixnum.
|
145
|
+
# Returns new HSB object with added HSB.
|
146
|
+
def +(arg)
|
147
|
+
arg =
|
148
|
+
case arg
|
149
|
+
when Array
|
150
|
+
raise ArgumentError, "Must be three numbers contained" unless arg.size==3
|
151
|
+
arg
|
152
|
+
else
|
153
|
+
raise ArgumentError, "Accept only Array of three numbers"
|
154
|
+
end
|
155
|
+
new_hsb = self.hsb.zip(arg, [360, 101, 101]).map { |x, y, div| (x + y) % div }
|
156
|
+
self.class.new *new_hsb
|
157
|
+
end
|
158
|
+
|
159
|
+
def to_name
|
160
|
+
rgb2name(self.to_rgb)
|
161
|
+
end
|
162
|
+
|
163
|
+
def to_rgb
|
164
|
+
hsb2rgb(self.to_a)
|
165
|
+
end
|
166
|
+
|
167
|
+
def to_hex
|
168
|
+
rgb2hex(self.to_rgb)
|
169
|
+
end
|
170
|
+
|
171
|
+
private
|
127
172
|
def validate_hsb(hsb)
|
128
173
|
validate([0..359, 0..100, 0..100], hsb)
|
129
174
|
end
|
130
|
-
|
175
|
+
end
|
131
176
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
177
|
+
class HEX < ColorSpace
|
178
|
+
attr_reader :hex
|
179
|
+
def initialize(hex='#FFFFFF')
|
180
|
+
@hex = validate_hex(hex)
|
181
|
+
end
|
182
|
+
alias :to_s :hex
|
138
183
|
|
139
|
-
|
140
|
-
|
141
|
-
|
184
|
+
def to_a
|
185
|
+
@hex.unpack('A1A2A2A2').drop(1)
|
186
|
+
end
|
142
187
|
|
143
|
-
|
144
|
-
|
145
|
-
|
188
|
+
def +(arg)
|
189
|
+
build_hex_with(:+, arg)
|
190
|
+
end
|
146
191
|
|
147
|
-
|
148
|
-
|
149
|
-
|
192
|
+
def -(arg)
|
193
|
+
build_hex_with(:-, arg)
|
194
|
+
end
|
150
195
|
|
151
|
-
|
152
|
-
|
153
|
-
|
196
|
+
def to_rgb
|
197
|
+
hex2rgb(self.to_s)
|
198
|
+
end
|
154
199
|
|
155
|
-
|
156
|
-
|
157
|
-
|
200
|
+
def to_hsb
|
201
|
+
rgb2hsb(self.to_rgb)
|
202
|
+
end
|
158
203
|
|
159
|
-
|
160
|
-
|
161
|
-
|
204
|
+
def to_name
|
205
|
+
rgb2name(self.to_rgb)
|
206
|
+
end
|
162
207
|
|
163
208
|
private
|
164
209
|
def validate_hex(hex)
|
165
|
-
|
210
|
+
hex = hex.join if hex.is_a?(Array)
|
166
211
|
validate(/^#[0-9A-F]{6}$/i, hex_norm(hex))
|
167
212
|
end
|
168
213
|
|
169
214
|
def hex_norm(hex)
|
170
|
-
|
171
|
-
|
172
|
-
|
215
|
+
hex = hex.to_s.sub(/^#/, '').upcase
|
216
|
+
.sub(/^([0-9A-F])([0-9A-F])([0-9A-F])$/, '\1\1\2\2\3\3')
|
217
|
+
"##{hex}"
|
173
218
|
end
|
174
219
|
|
175
220
|
def rgb2hex(rgb)
|
@@ -185,76 +230,76 @@ module Colorable
|
|
185
230
|
end
|
186
231
|
|
187
232
|
def build_hex_with(op, arg)
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
end
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
233
|
+
_rgb =
|
234
|
+
case arg
|
235
|
+
when Fixnum
|
236
|
+
[arg] * 3
|
237
|
+
when String
|
238
|
+
hex2rgb(validate_hex arg)
|
239
|
+
else
|
240
|
+
raise ArgumentError, "Accept only a Hex string or a Fixnum"
|
241
|
+
end
|
242
|
+
rgb = hex2rgb(self.hex).zip(_rgb).map { |x, y| (x.send(op, y)) % 256 }
|
243
|
+
self.class.new rgb2hex(rgb)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
class NAME < ColorSpace
|
248
|
+
attr_accessor :name
|
249
|
+
attr_reader :sym
|
250
|
+
def initialize(name)
|
251
|
+
@name = find_name(name)
|
252
|
+
@sym = nil
|
253
|
+
end
|
254
|
+
|
255
|
+
alias :to_s :name
|
256
|
+
|
257
|
+
def sym(sep='_')
|
258
|
+
@name.gsub(/\s/, sep).downcase.intern if @name
|
259
|
+
end
|
260
|
+
|
261
|
+
def dark?
|
217
262
|
DARK_COLORS.detect { |d| d == self.name }
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
263
|
+
end
|
264
|
+
|
265
|
+
def <=>(other)
|
266
|
+
self.name <=> other.name
|
267
|
+
end
|
268
|
+
|
269
|
+
def +(n)
|
270
|
+
raise ArgumentError, 'Only accept a Fixnum' unless n.is_a?(Fixnum)
|
271
|
+
pos = COLORNAMES.find_index{|n,_| n==self.name} + n
|
272
|
+
self.class.new COLORNAMES.at(pos % COLORNAMES.size)[0]
|
273
|
+
end
|
274
|
+
|
275
|
+
def -(n)
|
276
|
+
raise ArgumentError, 'Only accept a Fixnum' unless n.is_a?(Fixnum)
|
277
|
+
self + -n
|
278
|
+
end
|
279
|
+
|
280
|
+
def coerce(arg)
|
281
|
+
[self, arg]
|
282
|
+
end
|
283
|
+
|
284
|
+
def to_rgb
|
285
|
+
name2rgb(self.to_s)
|
286
|
+
end
|
287
|
+
|
288
|
+
def to_hsb
|
289
|
+
rgb2hsb(self.to_rgb)
|
290
|
+
end
|
291
|
+
|
292
|
+
def to_hex
|
293
|
+
rgb2hex(self.to_rgb)
|
294
|
+
end
|
295
|
+
|
296
|
+
private
|
297
|
+
def find_name(name)
|
298
|
+
COLORNAMES.map(&:first).detect { |c|
|
254
299
|
[c, name].same? { |str| "#{str}".gsub(/[_\s]/,'').downcase }
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
300
|
+
} || begin
|
301
|
+
raise ArgumentError, "'#{name}' is not in X11 colorset."
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
260
305
|
end
|