rbbt-util 5.2.4 → 5.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/bin/rbbt +23 -10
- data/bin/rbbt_monitor.rb +8 -8
- data/lib/rbbt/annotations.rb +22 -1
- data/lib/rbbt/annotations/util.rb +1 -1
- data/lib/rbbt/entity.rb +162 -0
- data/lib/rbbt/fix_width_table.rb +7 -0
- data/lib/rbbt/persist.rb +16 -9
- data/lib/rbbt/persist/tsv.rb +14 -8
- data/lib/rbbt/resource.rb +1 -6
- data/lib/rbbt/resource/path.rb +23 -27
- data/lib/rbbt/tsv.rb +33 -4
- data/lib/rbbt/tsv/accessor.rb +100 -57
- data/lib/rbbt/tsv/attach.rb +3 -1
- data/lib/rbbt/tsv/attach/util.rb +34 -10
- data/lib/rbbt/tsv/index.rb +12 -3
- data/lib/rbbt/tsv/manipulate.rb +25 -1
- data/lib/rbbt/tsv/parser.rb +1 -0
- data/lib/rbbt/util/R.rb +36 -6
- data/lib/rbbt/util/cmd.rb +2 -1
- data/lib/rbbt/util/color.rb +250 -0
- data/lib/rbbt/util/colorize.rb +57 -0
- data/lib/rbbt/util/misc.rb +57 -19
- data/lib/rbbt/util/named_array.rb +66 -14
- data/lib/rbbt/util/open.rb +134 -10
- data/lib/rbbt/util/semaphore.rb +71 -0
- data/lib/rbbt/workflow.rb +34 -7
- data/lib/rbbt/workflow/accessor.rb +12 -8
- data/lib/rbbt/workflow/step.rb +44 -28
- data/lib/rbbt/workflow/usage.rb +3 -0
- data/share/lib/R/util.R +31 -0
- data/share/rbbt_commands/app/start +5 -4
- data/share/rbbt_commands/study/task +222 -0
- data/share/rbbt_commands/tsv/attach +13 -0
- data/share/rbbt_commands/tsv/change_id +15 -0
- data/share/rbbt_commands/tsv/info +3 -1
- data/share/rbbt_commands/workflow/task +14 -15
- data/test/rbbt/test_entity.rb +221 -0
- data/test/rbbt/test_tsv.rb +2 -1
- data/test/rbbt/test_workflow.rb +0 -2
- data/test/rbbt/tsv/test_accessor.rb +2 -2
- data/test/rbbt/util/test_R.rb +9 -2
- data/test/rbbt/util/test_colorize.rb +12 -0
- data/test/rbbt/util/test_misc.rb +0 -5
- data/test/rbbt/util/test_open.rb +31 -0
- data/test/rbbt/workflow/test_step.rb +32 -0
- metadata +13 -2
data/lib/rbbt/tsv/attach.rb
CHANGED
@@ -145,7 +145,7 @@ module TSV
|
|
145
145
|
|
146
146
|
unless TSV === other
|
147
147
|
other_identifier_files = other.identifier_files if other.respond_to? :identifier_files
|
148
|
-
other = TSV.open(other, :persist => options[:persist_input] == true)
|
148
|
+
other = TSV.open(other, :persist => options[:persist_input] == true)
|
149
149
|
other.identifiers = other_identifier_files
|
150
150
|
end
|
151
151
|
|
@@ -161,6 +161,7 @@ module TSV
|
|
161
161
|
|
162
162
|
case
|
163
163
|
when key_field == other.key_field
|
164
|
+
Log.debug "Attachment with same key: #{other.key_field}"
|
164
165
|
attach_same_key other, fields
|
165
166
|
when (not in_namespace and self.fields.include?(other.key_field))
|
166
167
|
Log.debug "Found other's key field: #{other.key_field}"
|
@@ -171,6 +172,7 @@ module TSV
|
|
171
172
|
else
|
172
173
|
index = TSV.find_traversal(self, other, options)
|
173
174
|
raise "Cannot traverse identifiers" if index.nil?
|
175
|
+
Log.debug "Attachment with index: #{other.key_field}"
|
174
176
|
attach_index other, index, fields
|
175
177
|
end
|
176
178
|
Log.debug("Attachment of fields:#{fields.inspect} from #{other.filename.inspect} finished.")
|
data/lib/rbbt/tsv/attach/util.rb
CHANGED
@@ -10,10 +10,16 @@ module TSV
|
|
10
10
|
with_unnamed do
|
11
11
|
through do |key, values|
|
12
12
|
self[key] = [] if self[key].nil?
|
13
|
+
current = self[key]
|
14
|
+
current = [current] unless Array === current
|
13
15
|
if other.include? key
|
14
16
|
case
|
15
17
|
when other.type == :flat
|
16
|
-
|
18
|
+
if type == :flat
|
19
|
+
new_values = other[key]
|
20
|
+
else
|
21
|
+
new_values = [other[key]]
|
22
|
+
end
|
17
23
|
when other.type == :single
|
18
24
|
new_values = [other[key]]
|
19
25
|
else
|
@@ -23,19 +29,23 @@ module TSV
|
|
23
29
|
new_values.collect!{|v| [v]} if type == :double and not other.type == :double
|
24
30
|
new_values.collect!{|v| v.nil? ? nil : (other.type == :single ? v : v.first)} if not type == :double and other.type == :double
|
25
31
|
|
26
|
-
|
32
|
+
new_values.flatten if type == :flat
|
33
|
+
|
34
|
+
self[key] = current.concat new_values
|
27
35
|
else
|
28
36
|
if type == :double
|
29
|
-
self[key] =
|
37
|
+
self[key] = current.concat [[]] * fields.length
|
30
38
|
else
|
31
|
-
self[key] =
|
39
|
+
self[key] = current.concat [""] * fields.length
|
32
40
|
end
|
33
41
|
end
|
34
42
|
end
|
35
43
|
end
|
36
44
|
end
|
37
45
|
|
38
|
-
self.
|
46
|
+
self.type = :list if self.type == :single
|
47
|
+
|
48
|
+
self.fields = self.fields.concat fields
|
39
49
|
end
|
40
50
|
|
41
51
|
def attach_source_key(other, source, options = {})
|
@@ -141,11 +151,16 @@ module TSV
|
|
141
151
|
source_key
|
142
152
|
end
|
143
153
|
else
|
144
|
-
other
|
154
|
+
if other.type == :flat
|
155
|
+
other[source_key]
|
156
|
+
else
|
157
|
+
other[source_key][pos]
|
158
|
+
end
|
145
159
|
end
|
146
160
|
end
|
147
161
|
new_values.collect!{|v| v.nil? ? [[]] : [v]} if type == :double and not other.type == :double
|
148
162
|
new_values.collect!{|v| v.nil? ? nil : (other.type == :single ? v : v.first)} if not type == :double and other.type == :double
|
163
|
+
new_values.flatten! if type == :flat
|
149
164
|
all_new_values << new_values
|
150
165
|
end
|
151
166
|
end
|
@@ -160,6 +175,8 @@ module TSV
|
|
160
175
|
|
161
176
|
current = self[key] || [[]] * fields.length
|
162
177
|
|
178
|
+
current = [current] unless Array === current
|
179
|
+
|
163
180
|
if current.length > length
|
164
181
|
all_new_values << current.slice!(length..current.length - 1)
|
165
182
|
end
|
@@ -172,12 +189,14 @@ module TSV
|
|
172
189
|
|
173
190
|
current += all_new_values
|
174
191
|
|
175
|
-
self[key]
|
192
|
+
self[key].replace current
|
176
193
|
end
|
177
194
|
end
|
178
195
|
end
|
179
196
|
end
|
180
197
|
|
198
|
+
self.type = :list if self.type == :single
|
199
|
+
|
181
200
|
self.fields = self.fields.concat field_names
|
182
201
|
end
|
183
202
|
|
@@ -219,7 +238,7 @@ module TSV
|
|
219
238
|
end
|
220
239
|
|
221
240
|
def self.build_traverse_index(files, options = {})
|
222
|
-
options = Misc.add_defaults options, :in_namespace => false, :persist_input =>
|
241
|
+
options = Misc.add_defaults options, :in_namespace => false, :persist_input => true
|
223
242
|
in_namespace = options[:in_namespace]
|
224
243
|
persist_input = options[:persist_input]
|
225
244
|
|
@@ -237,7 +256,7 @@ module TSV
|
|
237
256
|
nil
|
238
257
|
else
|
239
258
|
Log.debug "Data index required"
|
240
|
-
data_file.index :target => data_key, :fields => [data_file.key_field], :persist => false
|
259
|
+
data_file.index :target => data_key, :fields => [data_file.key_field], :persist => false, :type => (data_file.type == :single ? :single : :flat)
|
241
260
|
end
|
242
261
|
|
243
262
|
current_index = data_index
|
@@ -257,7 +276,12 @@ module TSV
|
|
257
276
|
if values.nil?
|
258
277
|
nil
|
259
278
|
else
|
260
|
-
next_index.values_at(*values).flatten
|
279
|
+
new_values = next_index.values_at(*values).flatten
|
280
|
+
if current_index.type == :single
|
281
|
+
new_values.first
|
282
|
+
else
|
283
|
+
new_values
|
284
|
+
end
|
261
285
|
end
|
262
286
|
end
|
263
287
|
current_index.fields = [next_key]
|
data/lib/rbbt/tsv/index.rb
CHANGED
@@ -85,8 +85,8 @@ module TSV
|
|
85
85
|
values.unshift key
|
86
86
|
|
87
87
|
values.uniq.each do |value|
|
88
|
-
case
|
89
|
-
when
|
88
|
+
case index_type
|
89
|
+
when :double
|
90
90
|
if not new.include? value
|
91
91
|
new[value] = [[key]]
|
92
92
|
else
|
@@ -94,6 +94,15 @@ module TSV
|
|
94
94
|
current[0] << key
|
95
95
|
new[value] = current
|
96
96
|
end
|
97
|
+
when :flat
|
98
|
+
if not new.include? value
|
99
|
+
new[value] = [key]
|
100
|
+
else
|
101
|
+
current = new[value]
|
102
|
+
current << key
|
103
|
+
new[value] = current
|
104
|
+
end
|
105
|
+
|
97
106
|
else
|
98
107
|
new[value] = key unless new.include? value
|
99
108
|
end
|
@@ -101,7 +110,7 @@ module TSV
|
|
101
110
|
end
|
102
111
|
end
|
103
112
|
|
104
|
-
TSV.setup(new, :
|
113
|
+
TSV.setup(new, :type => index_type, :filename => filename, :fields => [new_key_field], :key_field => new_fields * ", ")
|
105
114
|
end
|
106
115
|
end
|
107
116
|
end
|
data/lib/rbbt/tsv/manipulate.rb
CHANGED
@@ -345,6 +345,8 @@ module TSV
|
|
345
345
|
end
|
346
346
|
else
|
347
347
|
pos = identify_field method
|
348
|
+
raise "Field #{ method } not identified. Available: #{ fields * ", " }" if pos.nil?
|
349
|
+
|
348
350
|
through do |key, values|
|
349
351
|
new[key] = values if yield(values[pos])
|
350
352
|
end
|
@@ -451,11 +453,32 @@ module TSV
|
|
451
453
|
end
|
452
454
|
end
|
453
455
|
|
454
|
-
|
456
|
+
case type
|
457
|
+
when :double, :flat
|
458
|
+
new.type = :flat
|
459
|
+
else
|
460
|
+
new.type = :single
|
461
|
+
end
|
455
462
|
|
456
463
|
new
|
457
464
|
end
|
458
465
|
|
466
|
+
def process_key(&block)
|
467
|
+
new = annotate({})
|
468
|
+
through do |key, values|
|
469
|
+
key = case
|
470
|
+
when block.arity == 1
|
471
|
+
yield(key)
|
472
|
+
when block.arity == 2
|
473
|
+
yield(key, values)
|
474
|
+
else
|
475
|
+
raise "Unexpected arity in block, must be 1, 2 or 3: #{block.arity}"
|
476
|
+
end
|
477
|
+
new[key] = values
|
478
|
+
end
|
479
|
+
new
|
480
|
+
end
|
481
|
+
|
459
482
|
def process(field, &block)
|
460
483
|
field_pos = identify_field field
|
461
484
|
|
@@ -498,6 +521,7 @@ module TSV
|
|
498
521
|
self[key] = values
|
499
522
|
end
|
500
523
|
end
|
524
|
+
|
501
525
|
self
|
502
526
|
end
|
503
527
|
|
data/lib/rbbt/tsv/parser.rb
CHANGED
data/lib/rbbt/util/R.rb
CHANGED
@@ -7,7 +7,12 @@ module R
|
|
7
7
|
UTIL = File.join(LIB_DIR, 'util.R')
|
8
8
|
|
9
9
|
def self.run(command, options = {})
|
10
|
-
cmd
|
10
|
+
cmd =<<-EOF
|
11
|
+
# Loading basic rbbt environment
|
12
|
+
source('#{UTIL}');
|
13
|
+
|
14
|
+
EOF
|
15
|
+
|
11
16
|
case
|
12
17
|
when IO === command
|
13
18
|
cmd << command.read
|
@@ -19,7 +24,15 @@ module R
|
|
19
24
|
|
20
25
|
Log.debug "R Script:\n#{ cmd }"
|
21
26
|
|
22
|
-
|
27
|
+
if options.delete :monitor
|
28
|
+
io = CMD.cmd('R --vanilla --slave --quiet', options.merge(:in => cmd, :pipe => true))
|
29
|
+
while line = io.gets
|
30
|
+
puts line
|
31
|
+
end
|
32
|
+
nil
|
33
|
+
else
|
34
|
+
CMD.cmd('R --vanilla --slave --quiet', options.merge(:in => cmd))
|
35
|
+
end
|
23
36
|
end
|
24
37
|
|
25
38
|
def self.interactive(init_file, options = {})
|
@@ -29,9 +42,9 @@ module R
|
|
29
42
|
def self.interactive(script, options = {})
|
30
43
|
TmpFile.with_file do |init_file|
|
31
44
|
Open.write(init_file) do |file|
|
32
|
-
|
33
|
-
file.puts "source('#{profile}');\n" if File.exists? profile
|
45
|
+
file.puts "# Loading basic rbbt environment"
|
34
46
|
file.puts "source('#{R::UTIL}');\n"
|
47
|
+
file.puts
|
35
48
|
file.puts script
|
36
49
|
end
|
37
50
|
CMD.cmd("env R_PROFILE='#{init_file}' xterm R")
|
@@ -40,12 +53,21 @@ module R
|
|
40
53
|
|
41
54
|
def self.ruby2R(object)
|
42
55
|
case object
|
56
|
+
when nil
|
57
|
+
"NULL"
|
58
|
+
when TSV
|
59
|
+
#"as.matrix(data.frame(c(#{object.transpose("Field").collect{|k,v| "#{k}=" << R.ruby2R(v)}.flatten * ", "}), row.names=#{R.ruby2R object.keys}))"
|
60
|
+
"matrix(#{R.ruby2R object.values},dimnames=list(#{R.ruby2R object.keys}, #{R.ruby2R object.fields}))"
|
61
|
+
when Symbol
|
62
|
+
"#{ object }"
|
43
63
|
when String
|
44
64
|
"'#{ object }'"
|
45
|
-
when Fixnum
|
65
|
+
when Fixnum, Float
|
46
66
|
object
|
47
67
|
when Array
|
48
68
|
"c(#{object.collect{|e| ruby2R(e) } * ", "})"
|
69
|
+
else
|
70
|
+
raise "Type of object not known: #{ object.inspect }"
|
49
71
|
end
|
50
72
|
end
|
51
73
|
|
@@ -58,13 +80,21 @@ module TSV
|
|
58
80
|
Open.write(f, self.to_s)
|
59
81
|
Log.debug(R.run(
|
60
82
|
<<-EOF
|
83
|
+
## Loading tsv into data
|
61
84
|
data = rbbt.tsv('#{f}');
|
85
|
+
|
62
86
|
#{script.strip}
|
87
|
+
|
88
|
+
## Resaving data
|
63
89
|
if (! is.null(data)){ rbbt.tsv.write('#{f}', data); }
|
64
90
|
EOF
|
65
91
|
).read)
|
66
92
|
open_options = Misc.add_defaults open_options, :type => :list
|
67
|
-
|
93
|
+
if open_options[:raw]
|
94
|
+
Open.read(f)
|
95
|
+
else
|
96
|
+
TSV.open(f, open_options) unless open_options[:ignore_output]
|
97
|
+
end
|
68
98
|
end
|
69
99
|
end
|
70
100
|
|
data/lib/rbbt/util/cmd.rb
CHANGED
@@ -105,6 +105,7 @@ module CMD
|
|
105
105
|
pipe = options.delete(:pipe)
|
106
106
|
post = options.delete(:post)
|
107
107
|
log = options.delete(:log)
|
108
|
+
dont_close_in = options.delete(:dont_close_in)
|
108
109
|
|
109
110
|
log = true if log.nil?
|
110
111
|
|
@@ -186,7 +187,7 @@ module CMD
|
|
186
187
|
end
|
187
188
|
|
188
189
|
sin.close unless sin.closed?
|
189
|
-
in_content.close unless in_content.closed?
|
190
|
+
in_content.close unless in_content.closed? or dont_close_in
|
190
191
|
rescue
|
191
192
|
Process.kill "INT", pid
|
192
193
|
raise $!
|
@@ -0,0 +1,250 @@
|
|
1
|
+
# Copyright (c) 2007 McClain Looney
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
|
22
|
+
# Implements a color (r,g,b + a) with conversion to/from web format (eg #aabbcc), and
|
23
|
+
# with a number of utilities to lighten, darken and blend values.
|
24
|
+
class Color
|
25
|
+
|
26
|
+
attr_reader :r, :g, :b, :a
|
27
|
+
|
28
|
+
# Table for conversion to hex
|
29
|
+
HEXVAL = (('0'..'9').to_a).concat(('A'..'F').to_a).freeze
|
30
|
+
# Default value for #darken, #lighten etc.
|
31
|
+
BRIGHTNESS_DEFAULT = 0.2
|
32
|
+
|
33
|
+
# Constructor. Inits to white (#FFFFFF) by default, or accepts any params
|
34
|
+
# supported by #parse.
|
35
|
+
def initialize(*args)
|
36
|
+
@r = 255
|
37
|
+
@g = 255
|
38
|
+
@b = 255
|
39
|
+
@a = 255
|
40
|
+
|
41
|
+
if args.size.between?(3,4)
|
42
|
+
self.r = args[0]
|
43
|
+
self.g = args[1]
|
44
|
+
self.b = args[2]
|
45
|
+
self.a = args[3] if args[3]
|
46
|
+
else
|
47
|
+
set(*args)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# All-purpose setter - pass in another Color, '#000000', rgb vals... whatever
|
52
|
+
def set(*args)
|
53
|
+
val = Color.parse(*args)
|
54
|
+
unless val.nil?
|
55
|
+
self.r = val.r
|
56
|
+
self.g = val.g
|
57
|
+
self.b = val.b
|
58
|
+
self.a = val.a
|
59
|
+
end
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
# Test for equality, accepts string vals as well, eg Color.new('aaa') == '#AAAAAA' => true
|
64
|
+
def ==(val)
|
65
|
+
val = Color.parse(val)
|
66
|
+
return false if val.nil?
|
67
|
+
return r == val.r && g == val.g && b == val.b && a == val.a
|
68
|
+
end
|
69
|
+
|
70
|
+
# Setters for individual channels - take 0-255 or '00'-'FF' values
|
71
|
+
def r=(val); @r = from_hex(val); end
|
72
|
+
def g=(val); @g = from_hex(val); end
|
73
|
+
def b=(val); @b = from_hex(val); end
|
74
|
+
def a=(val); @a = from_hex(val); end
|
75
|
+
|
76
|
+
# Attempt to read in a string and parse it into values
|
77
|
+
def self.parse(*args)
|
78
|
+
case args.size
|
79
|
+
|
80
|
+
when 0 then
|
81
|
+
return nil
|
82
|
+
|
83
|
+
when 1 then
|
84
|
+
val = args[0]
|
85
|
+
|
86
|
+
# Trivial parse... :-)
|
87
|
+
return val if val.is_a?(Color)
|
88
|
+
|
89
|
+
# Single value, assume grayscale
|
90
|
+
return Color.new(val, val, val) if val.is_a?(Fixnum)
|
91
|
+
|
92
|
+
# Assume string
|
93
|
+
str = val.to_s.upcase
|
94
|
+
str = str[/[0-9A-F]{3,8}/] || ''
|
95
|
+
case str.size
|
96
|
+
when 3, 4 then
|
97
|
+
r, g, b, a = str.scan(/[0-9A-F]/)
|
98
|
+
when 6,8 then
|
99
|
+
r, g, b, a = str.scan(/[0-9A-F]{2}/)
|
100
|
+
else
|
101
|
+
return nil
|
102
|
+
end
|
103
|
+
|
104
|
+
return Color.new(r,g,b,a || 255)
|
105
|
+
|
106
|
+
when 3,4 then
|
107
|
+
return Color.new(*args)
|
108
|
+
|
109
|
+
end
|
110
|
+
nil
|
111
|
+
end
|
112
|
+
|
113
|
+
def inspect
|
114
|
+
to_s(true)
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_s(add_hash = true)
|
118
|
+
trans? ? to_rgba(add_hash) : to_rgb(add_hash)
|
119
|
+
end
|
120
|
+
|
121
|
+
def to_rgb(add_hash = true)
|
122
|
+
(add_hash ? '#' : '') + to_hex(r) + to_hex(g) + to_hex(b)
|
123
|
+
end
|
124
|
+
|
125
|
+
def to_rgba(add_hash = true)
|
126
|
+
to_rgb(add_hash) + to_hex(a)
|
127
|
+
end
|
128
|
+
|
129
|
+
def opaque?
|
130
|
+
@a == 255
|
131
|
+
end
|
132
|
+
|
133
|
+
def trans?
|
134
|
+
@a != 255
|
135
|
+
end
|
136
|
+
|
137
|
+
def grayscale?
|
138
|
+
@r == @g && @g == @b
|
139
|
+
end
|
140
|
+
|
141
|
+
# Lighten color towards white. 0.0 is a no-op, 1.0 will return #FFFFFF
|
142
|
+
def lighten(amt = BRIGHTNESS_DEFAULT)
|
143
|
+
return self if amt <= 0
|
144
|
+
return WHITE if amt >= 1.0
|
145
|
+
val = Color.new(self)
|
146
|
+
val.r += ((255-val.r) * amt).to_i
|
147
|
+
val.g += ((255-val.g) * amt).to_i
|
148
|
+
val.b += ((255-val.b) * amt).to_i
|
149
|
+
val
|
150
|
+
end
|
151
|
+
|
152
|
+
# In place version of #lighten
|
153
|
+
def lighten!(amt = BRIGHTNESS_DEFAULT)
|
154
|
+
set(lighten(amt))
|
155
|
+
self
|
156
|
+
end
|
157
|
+
|
158
|
+
# Darken a color towards full black. 0.0 is a no-op, 1.0 will return #000000
|
159
|
+
def darken(amt = BRIGHTNESS_DEFAULT)
|
160
|
+
return self if amt <= 0
|
161
|
+
return BLACK if amt >= 1.0
|
162
|
+
val = Color.new(self)
|
163
|
+
val.r -= (val.r * amt).to_i
|
164
|
+
val.g -= (val.g * amt).to_i
|
165
|
+
val.b -= (val.b * amt).to_i
|
166
|
+
val
|
167
|
+
end
|
168
|
+
|
169
|
+
# In place version of #darken
|
170
|
+
def darken!(amt = BRIGHTNESS_DEFAULT)
|
171
|
+
set(darken(amt))
|
172
|
+
self
|
173
|
+
end
|
174
|
+
|
175
|
+
# Convert to grayscale, using perception-based weighting
|
176
|
+
def grayscale
|
177
|
+
val = Color.new(self)
|
178
|
+
val.r = val.g = val.b = (0.2126 * val.r + 0.7152 * val.g + 0.0722 * val.b)
|
179
|
+
val
|
180
|
+
end
|
181
|
+
|
182
|
+
# In place version of #grayscale
|
183
|
+
def grayscale!
|
184
|
+
set(grayscale)
|
185
|
+
self
|
186
|
+
end
|
187
|
+
|
188
|
+
# Blend to a color amt % towards another color value, eg
|
189
|
+
# red.blend(blue, 0.5) will be purple, white.blend(black, 0.5) will be gray, etc.
|
190
|
+
def blend(other, amt)
|
191
|
+
other = Color.parse(other)
|
192
|
+
return Color.new(self) if amt <= 0 || other.nil?
|
193
|
+
return Color.new(other) if amt >= 1.0
|
194
|
+
val = Color.new(self)
|
195
|
+
val.r += ((other.r - val.r)*amt).to_i
|
196
|
+
val.g += ((other.g - val.g)*amt).to_i
|
197
|
+
val.b += ((other.b - val.b)*amt).to_i
|
198
|
+
val
|
199
|
+
end
|
200
|
+
|
201
|
+
# In place version of #blend
|
202
|
+
def blend!(other, amt)
|
203
|
+
set(blend(other, amt))
|
204
|
+
self
|
205
|
+
end
|
206
|
+
|
207
|
+
# Class-level version for explicit blends of two values, useful with constants
|
208
|
+
def self.blend(col1, col2, amt)
|
209
|
+
col1 = Color.parse(col1)
|
210
|
+
col2 = Color.parse(col2)
|
211
|
+
col1.blend(col2, amt)
|
212
|
+
end
|
213
|
+
|
214
|
+
protected
|
215
|
+
|
216
|
+
# Convert int to string hex, eg 255 => 'FF'
|
217
|
+
def to_hex(val)
|
218
|
+
HEXVAL[val / 16] + HEXVAL[val % 16]
|
219
|
+
end
|
220
|
+
|
221
|
+
# Convert int or string to int, eg 80 => 80, 'FF' => 255, '7' => 119
|
222
|
+
def from_hex(val)
|
223
|
+
if val.is_a?(String)
|
224
|
+
# Double up if single char form
|
225
|
+
val = val + val if val.size == 1
|
226
|
+
# Convert to integer
|
227
|
+
val = val.hex
|
228
|
+
end
|
229
|
+
# Clamp
|
230
|
+
val = 0 if val < 0
|
231
|
+
val = 255 if val > 255
|
232
|
+
val
|
233
|
+
end
|
234
|
+
|
235
|
+
public
|
236
|
+
|
237
|
+
# Some constants for general use
|
238
|
+
WHITE = Color.new(255,255,255).freeze
|
239
|
+
BLACK = Color.new(0,0,0).freeze
|
240
|
+
|
241
|
+
end
|
242
|
+
|
243
|
+
# "Global" method for creating Color objects, eg:
|
244
|
+
# new_color = rgb(params[:new_color])
|
245
|
+
# style="border: 1px solid <%= rgb(10,50,80).lighten %>"
|
246
|
+
def rgb(*args)
|
247
|
+
Color.parse(*args)
|
248
|
+
end
|
249
|
+
|
250
|
+
|