coloryze 2.0.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.
- 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
|