coloryze 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +0 -0
- data/bin/coloryze +16 -0
- data/lib/color.rb +16 -0
- data/lib/color/coloryze.rb +225 -0
- data/lib/color/full_palette.rb +158 -0
- data/lib/color/hsb.rb +107 -0
- data/lib/color/palette.rb +92 -0
- data/lib/color/rgb.rb +133 -0
- data/lib/color/sample_palette.rb +78 -0
- data/lib/color/scheme.rb +124 -0
- data/lib/color/util.rb +30 -0
- data/test/color/coloryze_test.rb +90 -0
- data/test/color/full_palette_test.rb +153 -0
- data/test/color/hsb_test.rb +54 -0
- data/test/color/rgb_test.rb +104 -0
- data/test/color/sample_palette_test.rb +64 -0
- data/test/color/scheme_test.rb +95 -0
- data/test/color/util_test.rb +33 -0
- metadata +105 -0
@@ -0,0 +1,92 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require 'color/hsb'
|
5
|
+
require 'color/rgb'
|
6
|
+
require 'color/util'
|
7
|
+
|
8
|
+
# shows a set of colors, in text and html
|
9
|
+
|
10
|
+
module Color
|
11
|
+
|
12
|
+
class Palette
|
13
|
+
|
14
|
+
attr_reader :background
|
15
|
+
attr_reader :saturation_start
|
16
|
+
attr_reader :saturation_end
|
17
|
+
attr_reader :saturation_step
|
18
|
+
attr_reader :brightness_start
|
19
|
+
attr_reader :brightness_end
|
20
|
+
attr_reader :brightness_step
|
21
|
+
|
22
|
+
def initialize(args = Hash.new)
|
23
|
+
@background = args[:background]
|
24
|
+
|
25
|
+
@saturation_start = args[:saturation_start] || 0
|
26
|
+
@saturation_end = args[:saturation_end] || 100
|
27
|
+
@saturation_step = args[:saturation_step] || 25
|
28
|
+
|
29
|
+
@brightness_start = args[:brightness_start] || 0
|
30
|
+
@brightness_end = args[:brightness_end] || 100
|
31
|
+
@brightness_step = args[:brightness_step] || 25
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns nested hashes: hue => saturation => brightness => rgb
|
35
|
+
def colors_by_hue
|
36
|
+
by_hue = Hash.new
|
37
|
+
|
38
|
+
colors.each do |color|
|
39
|
+
by_hue[color.hue] ||= Hash.new
|
40
|
+
by_hue[color.hue][color.saturation] ||= Hash.new
|
41
|
+
by_hue[color.hue][color.saturation][color.brightness] = color
|
42
|
+
end
|
43
|
+
|
44
|
+
by_hue
|
45
|
+
end
|
46
|
+
|
47
|
+
def print_as_text(io = $stdout)
|
48
|
+
colors.sort.each do |hsb|
|
49
|
+
io.printf "%4.1f %3d %3d %06x\n", hsb.hue, hsb.saturation, hsb.brightness, hsb.to_rgb
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def print_as_html(io = $stdout)
|
54
|
+
puts "@background: #{@background}"
|
55
|
+
|
56
|
+
bg = @background || "FFFFFF"
|
57
|
+
io.puts "<div style=\"background-color: " + bg + ";\">"
|
58
|
+
|
59
|
+
print_html_tables(io)
|
60
|
+
|
61
|
+
io.puts "</div>"
|
62
|
+
end
|
63
|
+
|
64
|
+
def print_html_hue_line(io, hue, colspan)
|
65
|
+
# if no background or is A+ (dark), then
|
66
|
+
textcolor = @background.nil? || @background.index(%r{[A-Z]\w}) ? "000000" : "FFFFFF"
|
67
|
+
io.puts " <td colspan=\"" + colspan.to_s + "\" style=\"color: #" + textcolor + ";\">hue " + hue.to_s + "°</td>"
|
68
|
+
end
|
69
|
+
|
70
|
+
def print_html_color(io, saturation, brightness, rgb, cellwidth)
|
71
|
+
rgbstr = sprintf("%06x", rgb)
|
72
|
+
io.puts " <td style=\"width: " + cellwidth.to_s + "%; vertical-align: bottom; border:2px outset; border-collapse:collapse;\">"
|
73
|
+
io.puts " <table width=\"100%\" border=\"0\" border-width=\"4px\">"
|
74
|
+
io.print " <tr><td"
|
75
|
+
if @background
|
76
|
+
print "style=\"color: " + rgbstr + ";\""
|
77
|
+
end
|
78
|
+
io.printf ">#%s; s: %3.2f; b: %3.2f\n</td></tr>", rgbstr, saturation, brightness
|
79
|
+
io.puts " <tr><td style=\"background-color: " + rgbstr + "; vertical-align: bottom;\"><br> </td></tr>"
|
80
|
+
io.puts " </table>"
|
81
|
+
io.puts " </td>"
|
82
|
+
end
|
83
|
+
|
84
|
+
#$$$ todo: change to return hsbs
|
85
|
+
|
86
|
+
def colors
|
87
|
+
raise "abstract method: colors"
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
data/lib/color/rgb.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require 'color/util'
|
5
|
+
require 'color/hsb'
|
6
|
+
require 'rubygems'
|
7
|
+
require 'riel'
|
8
|
+
|
9
|
+
module Color
|
10
|
+
|
11
|
+
class RGB
|
12
|
+
include Loggable
|
13
|
+
|
14
|
+
attr_accessor :red, :green, :blue
|
15
|
+
|
16
|
+
RGB_VALUES = '([0-9a-f]{2})'
|
17
|
+
# not supported, although in CSS: RGB_REGEXP = Regexp.new '(' + RGB_VALUES + '){3}', Regexp::IGNORECASE
|
18
|
+
RRGGBB_REGEXP = Regexp.new '^\#?' + (RGB_VALUES * 3) + '$', Regexp::IGNORECASE # '$
|
19
|
+
|
20
|
+
class << self
|
21
|
+
alias_method :new_orig, :new
|
22
|
+
def new(*args)
|
23
|
+
if args
|
24
|
+
if args.size == 3
|
25
|
+
new_orig(*args)
|
26
|
+
elsif args.size == 1 and args[0].kind_of? String
|
27
|
+
str = args[0].sub(%r{^\#}, '') # optional
|
28
|
+
# str.hex does not validate, so we do here:
|
29
|
+
if md = matches_rgb_string?(str)
|
30
|
+
r, g, b = (1 .. 3).collect { |idx| md[idx].hex }
|
31
|
+
new_orig r, g, b
|
32
|
+
else
|
33
|
+
raise RuntimeError.new("invalid RGB string '#{str}: does not match #{RRGGBB_REGEXP.source}")
|
34
|
+
end
|
35
|
+
else
|
36
|
+
raise RuntimeError.new("invalid arguments '#{args.inspect}': expecting 0, 1, or 3")
|
37
|
+
end
|
38
|
+
else
|
39
|
+
new_orig 0, 0, 0
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def matches_rgb_string?(val)
|
44
|
+
val.kind_of?(String) && RRGGBB_REGEXP.match(val)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(red, green, blue)
|
49
|
+
[ [ red, "red" ], [ green, "green" ], [ blue, "blue" ] ].each do |val, str|
|
50
|
+
Color::check_constraint(val, str, 0, 255)
|
51
|
+
end
|
52
|
+
|
53
|
+
@red = red.to_i
|
54
|
+
@green = green.to_i
|
55
|
+
@blue = blue.to_i
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_s
|
59
|
+
"#{red},#{green},#{blue}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_i
|
63
|
+
(red << 16) | (green << 8) | blue
|
64
|
+
end
|
65
|
+
|
66
|
+
def <=>(other)
|
67
|
+
to_i <=> other.to_i
|
68
|
+
end
|
69
|
+
|
70
|
+
def ==(other)
|
71
|
+
to_i == other.to_i
|
72
|
+
end
|
73
|
+
|
74
|
+
# Converts RGB to an HSB object.
|
75
|
+
def to_hsb
|
76
|
+
# this is not the only implementation of RGB to HSB, but it works.
|
77
|
+
|
78
|
+
# scale from 0..255 to 0..1
|
79
|
+
r, g, b = [ red, green, blue ].collect { |v| v / 255.0 }
|
80
|
+
|
81
|
+
log "r", r
|
82
|
+
log "g", g
|
83
|
+
log "b", b
|
84
|
+
|
85
|
+
minrgb = [ r, g, b ].min
|
86
|
+
log "minrgb", minrgb
|
87
|
+
maxrgb = [ r, g, b ].max
|
88
|
+
log "maxrgb", maxrgb
|
89
|
+
delta = maxrgb - minrgb
|
90
|
+
log "delta", delta
|
91
|
+
|
92
|
+
hue = nil
|
93
|
+
saturation = nil
|
94
|
+
brightness = maxrgb
|
95
|
+
|
96
|
+
if delta == 0
|
97
|
+
# this is a gray, no chroma...
|
98
|
+
hue = 0
|
99
|
+
saturation = 0
|
100
|
+
else # chromatic data...
|
101
|
+
saturation = delta / maxrgb
|
102
|
+
|
103
|
+
del_red, del_green, del_blue = [ r, g, b ].collect do |v|
|
104
|
+
(((maxrgb - v) / 6.0) + (delta / 2.0)) / delta
|
105
|
+
end
|
106
|
+
|
107
|
+
if r == maxrgb
|
108
|
+
hue = del_blue - del_green
|
109
|
+
elsif g == maxrgb
|
110
|
+
hue = (1.0 / 3) + del_red - del_blue
|
111
|
+
elsif b == maxrgb
|
112
|
+
hue = (2.0 / 3) + del_green - del_red
|
113
|
+
end
|
114
|
+
|
115
|
+
if hue < 0
|
116
|
+
hue += 1
|
117
|
+
end
|
118
|
+
|
119
|
+
if hue > 1
|
120
|
+
hue -= 1
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
log "hue", hue
|
125
|
+
log "saturation", saturation
|
126
|
+
log "brightness", brightness
|
127
|
+
|
128
|
+
# hue is to one decimal place; saturation and brightness to three
|
129
|
+
Color::HSB.new Color::round(hue * 360, 1), Color::round(saturation, 3), Color::round(brightness, 3)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require 'color/hsb'
|
5
|
+
require 'color/rgb'
|
6
|
+
require 'color/util'
|
7
|
+
require 'color/palette'
|
8
|
+
require 'rubygems'
|
9
|
+
require 'riel'
|
10
|
+
|
11
|
+
# shows a set of colors, in text and html
|
12
|
+
|
13
|
+
module Color
|
14
|
+
|
15
|
+
class SamplePalette < Palette
|
16
|
+
include Loggable
|
17
|
+
|
18
|
+
def initialize(hues, args = Hash.new)
|
19
|
+
super(args)
|
20
|
+
|
21
|
+
n_samples = args[:samples]
|
22
|
+
n_total_samples = args[:total_samples]
|
23
|
+
n_per_hue = if n_samples
|
24
|
+
hues.collect { n_samples }
|
25
|
+
else
|
26
|
+
Color::distribution(n_total_samples, hues.length)
|
27
|
+
end
|
28
|
+
|
29
|
+
info "n_per_hue: #{n_per_hue}"
|
30
|
+
|
31
|
+
@colors = Array.new
|
32
|
+
|
33
|
+
hues.each_with_index do |hue, idx|
|
34
|
+
n_per_hue[idx].times do
|
35
|
+
# don't keep trying ...
|
36
|
+
10.times do
|
37
|
+
sat = Color::random_in_range(saturation_start, saturation_end, saturation_step)
|
38
|
+
brt = Color::random_in_range(brightness_start, brightness_end, brightness_step)
|
39
|
+
|
40
|
+
hsb = HSB.new hue, sat / 100.0, brt / 100.0
|
41
|
+
|
42
|
+
if @colors.include?(hsb)
|
43
|
+
# try, try again
|
44
|
+
else
|
45
|
+
@colors << hsb
|
46
|
+
break
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def colors
|
54
|
+
@colors
|
55
|
+
end
|
56
|
+
|
57
|
+
def print_html_tables(io = $stdout)
|
58
|
+
n_columns = 1 + (@brightness_end - @brightness_start) / @brightness_step
|
59
|
+
|
60
|
+
colors_by_hue.sort.each do |hue, by_sat|
|
61
|
+
io.puts "<table width=\"100%\">"
|
62
|
+
io.puts " <tr>"
|
63
|
+
print_html_hue_line(io, hue, 1)
|
64
|
+
io.puts " </tr>"
|
65
|
+
|
66
|
+
by_sat.sort.each do |sat, by_brt|
|
67
|
+
by_brt.sort.reverse.each do |brt, hsb|
|
68
|
+
io.puts " <tr>"
|
69
|
+
print_html_color(io, sat, brt, hsb.to_rgb, 100)
|
70
|
+
io.puts " </tr>"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
io.puts "</table>"
|
74
|
+
io.puts "<br/>"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/color/scheme.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
# creates schemes
|
5
|
+
|
6
|
+
require 'color/rgb'
|
7
|
+
|
8
|
+
module Color
|
9
|
+
|
10
|
+
# classes or methods, or factory?
|
11
|
+
|
12
|
+
class Scheme
|
13
|
+
include Loggable
|
14
|
+
|
15
|
+
# creates a hue from the value, processing it as a hue (0-360) or as
|
16
|
+
# RRGGBB, or generating a random one, if nil or an empty string.
|
17
|
+
|
18
|
+
def self.create_hue(value)
|
19
|
+
if value.nil?
|
20
|
+
(rand * 360).round
|
21
|
+
elsif value.kind_of? Numeric
|
22
|
+
value.round % 360
|
23
|
+
elsif value.kind_of? String
|
24
|
+
if %r{^\d{3}$}.match(value)
|
25
|
+
value.to_i.round % 360
|
26
|
+
elsif md = Color::RGB::RRGGBB_REGEXP.match(value)
|
27
|
+
Color::RGB.new(value).to_hsb.hue
|
28
|
+
else
|
29
|
+
raise RuntimeError.new("invalid hue value '#{value}")
|
30
|
+
end
|
31
|
+
else
|
32
|
+
raise RuntimeError.new("invalid hue value '#{value}")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :hues
|
37
|
+
|
38
|
+
def initialize
|
39
|
+
@hues = Array.new
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_hue(val)
|
43
|
+
hue = self.class.create_hue(val)
|
44
|
+
@hues << hue
|
45
|
+
hue
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Monochromatic < Scheme
|
50
|
+
def initialize(val)
|
51
|
+
super()
|
52
|
+
|
53
|
+
add_hue(val)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Complementary < Scheme
|
58
|
+
def initialize(val)
|
59
|
+
super()
|
60
|
+
|
61
|
+
hue = add_hue(val)
|
62
|
+
add_hue(hue + 180)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Triadic < Scheme
|
67
|
+
def initialize(val)
|
68
|
+
super()
|
69
|
+
|
70
|
+
hue = add_hue(val)
|
71
|
+
add_hue(hue + 120)
|
72
|
+
add_hue(hue + 240)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class SplitComplementary < Scheme
|
77
|
+
def initialize(val, deg)
|
78
|
+
super()
|
79
|
+
|
80
|
+
hue = add_hue(val)
|
81
|
+
add_hue(hue + 180 + deg)
|
82
|
+
add_hue(hue + 180 - deg)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class DoubleComplementary < Scheme
|
87
|
+
def initialize(val0, val1)
|
88
|
+
super()
|
89
|
+
|
90
|
+
hue0 = add_hue(val0)
|
91
|
+
hue1 = add_hue(val1)
|
92
|
+
|
93
|
+
add_hue(hue0 + 180)
|
94
|
+
add_hue(hue1 + 180)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class Analogous < Scheme
|
99
|
+
def initialize(val, deg)
|
100
|
+
super()
|
101
|
+
|
102
|
+
hue = add_hue(val)
|
103
|
+
|
104
|
+
add_hue(hue - deg)
|
105
|
+
add_hue(hue + deg)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class Hues < Scheme
|
110
|
+
def initialize(startval, endval, interval)
|
111
|
+
super()
|
112
|
+
|
113
|
+
starthue = self.class.create_hue(startval)
|
114
|
+
info "starthue: #{starthue}"
|
115
|
+
endhue = self.class.create_hue(endval)
|
116
|
+
info "endhue: #{endhue}"
|
117
|
+
|
118
|
+
(starthue .. endhue).step(interval) do |hue|
|
119
|
+
add_hue(hue)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
data/lib/color/util.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
module Color # should be riel
|
5
|
+
|
6
|
+
def self.check_constraint(val, str, minimum, maximum)
|
7
|
+
if val < minimum || val > maximum
|
8
|
+
raise RuntimeError.new("#{str} must be (#{minimum} .. #{maximum}), not: #{val}")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.round(num, places)
|
13
|
+
(num * 10 ** places).round / (10 ** places).to_f
|
14
|
+
end
|
15
|
+
|
16
|
+
# returns an array, of length len, of numbers, equally (or near-equally)
|
17
|
+
# distributed for the total
|
18
|
+
|
19
|
+
def self.distribution(total, len)
|
20
|
+
base_per = total.to_f / len
|
21
|
+
pivot = total - (base_per.floor * len)
|
22
|
+
|
23
|
+
(0 ... len).collect { |idx| base_per.floor + (idx < pivot ? 1 : 0) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.random_in_range(from, to, interval)
|
27
|
+
from + rand((to.to_f - from) / interval).round * interval
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|