spectrum 0.0.1
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/History.txt +93 -0
- data/Install.txt +18 -0
- data/Licence.txt +27 -0
- data/README.txt +35 -0
- data/lib/spectrum.rb +145 -0
- data/lib/spectrum/cmyk.rb +281 -0
- data/lib/spectrum/css.rb +28 -0
- data/lib/spectrum/grayscale.rb +212 -0
- data/lib/spectrum/hsl.rb +221 -0
- data/lib/spectrum/palette.rb +16 -0
- data/lib/spectrum/palette/adobecolor.rb +272 -0
- data/lib/spectrum/palette/gimp.rb +116 -0
- data/lib/spectrum/palette/monocontrast.rb +180 -0
- data/lib/spectrum/rgb-colors.rb +355 -0
- data/lib/spectrum/rgb.rb +453 -0
- data/lib/spectrum/rgb/metallic.rb +43 -0
- data/lib/spectrum/yiq.rb +84 -0
- data/test/test_adobecolor.rb +419 -0
- data/test/test_all.rb +23 -0
- data/test/test_cmyk.rb +128 -0
- data/test/test_color.rb +141 -0
- data/test/test_css.rb +29 -0
- data/test/test_gimp.rb +101 -0
- data/test/test_grayscale.rb +121 -0
- data/test/test_hsl.rb +153 -0
- data/test/test_monocontrast.rb +142 -0
- data/test/test_rgb.rb +344 -0
- data/test/test_yiq.rb +73 -0
- metadata +108 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
#--
|
2
|
+
# Spectrum
|
3
|
+
# Colour management with Ruby
|
4
|
+
# http://rubyforge.org/projects/color
|
5
|
+
# Version 1.4.1
|
6
|
+
#
|
7
|
+
# Licensed under a MIT-style licence. See Licence.txt in the main
|
8
|
+
# distribution for full licensing information.
|
9
|
+
#
|
10
|
+
# Copyright (c) 2005 - 2010 Austin Ziegler and Matt Lyon
|
11
|
+
#++
|
12
|
+
|
13
|
+
require 'spectrum'
|
14
|
+
|
15
|
+
module Spectrum::Palette
|
16
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
#--
|
2
|
+
# Spectrum
|
3
|
+
# Colour management with Ruby
|
4
|
+
# http://rubyforge.org/projects/color
|
5
|
+
# Version 1.4.1
|
6
|
+
#
|
7
|
+
# Licensed under a MIT-style licence. See Licence.txt in the main
|
8
|
+
# distribution for full licensing information.
|
9
|
+
#
|
10
|
+
# Copyright (c) 2005 - 2010 Austin Ziegler and Matt Lyon
|
11
|
+
#++
|
12
|
+
|
13
|
+
require 'spectrum/palette'
|
14
|
+
|
15
|
+
# A class that can read an Adobe Color palette file (used for Photoshop
|
16
|
+
# swatches) and provide a Hash-like interface to the contents. Not all
|
17
|
+
# colour formats in ACO files are supported. Based largely off the
|
18
|
+
# information found by Larry Tesler[http://www.nomodes.com/aco.html].
|
19
|
+
#
|
20
|
+
# Not all Adobe Color files have named colours; all named entries are
|
21
|
+
# returned as an array.
|
22
|
+
#
|
23
|
+
# pal = Spectrum::Palette::AdobeColor.from_file(my_aco_palette)
|
24
|
+
# pal[0] => Spectrum::RGB<...>
|
25
|
+
# pal["white"] => [ Spectrum::RGB<...> ]
|
26
|
+
# pal["unknown"] => [ Spectrum::RGB<...>, Spectrum::RGB<...>, ... ]
|
27
|
+
#
|
28
|
+
# AdobeColor palettes are always indexable by insertion order (an integer
|
29
|
+
# key).
|
30
|
+
#
|
31
|
+
# Version 2 palettes use UTF-16 colour names.
|
32
|
+
class Spectrum::Palette::AdobeColor
|
33
|
+
include Enumerable
|
34
|
+
|
35
|
+
class << self
|
36
|
+
# Create an AdobeColor palette object from the named file.
|
37
|
+
def from_file(filename)
|
38
|
+
File.open(filename, "rb") { |io| Spectrum::Palette::AdobeColor.from_io(io) }
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create an AdobeColor palette object from the provided IO.
|
42
|
+
def from_io(io)
|
43
|
+
Spectrum::Palette::AdobeColor.new(io.read)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns statistics about the nature of the colours loaded.
|
48
|
+
attr_reader :statistics
|
49
|
+
# Contains the "lost" colours in the palette. These colours could not be
|
50
|
+
# properly loaded (e.g., L*a*b* is not supported by Color, so it is
|
51
|
+
# "lost") or are not understood by the algorithms.
|
52
|
+
attr_reader :lost
|
53
|
+
|
54
|
+
# Use this to convert the unsigned word to the signed word, if necessary.
|
55
|
+
UwToSw = proc { |n| (n >= (2 ** 16)) ? n - (2 ** 32) : n } #:nodoc:
|
56
|
+
|
57
|
+
# Create a new AdobeColor palette from the palette file as a string.
|
58
|
+
def initialize(palette)
|
59
|
+
@colors = []
|
60
|
+
@names = {}
|
61
|
+
@statistics = Hash.new(0)
|
62
|
+
@lost = []
|
63
|
+
@order = []
|
64
|
+
@version = nil
|
65
|
+
|
66
|
+
class << palette
|
67
|
+
def readwords(count = 1)
|
68
|
+
@offset ||= 0
|
69
|
+
raise IndexError if @offset >= self.size
|
70
|
+
val = self[@offset, count * 2]
|
71
|
+
raise IndexError if val.nil? or val.size < (count * 2)
|
72
|
+
val = val.unpack("n" * count)
|
73
|
+
@offset += count * 2
|
74
|
+
val
|
75
|
+
end
|
76
|
+
|
77
|
+
def readutf16(count = 1)
|
78
|
+
@offset ||= 0
|
79
|
+
raise IndexError if @offset >= self.size
|
80
|
+
val = self[@offset, count * 2]
|
81
|
+
raise IndexError if val.nil? or val.size < (count * 2)
|
82
|
+
@offset += count * 2
|
83
|
+
val
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
@version, count = palette.readwords 2
|
88
|
+
|
89
|
+
raise "Unknown AdobeColor palette version #@version." unless @version.between?(1, 2)
|
90
|
+
|
91
|
+
count.times do
|
92
|
+
space, w, x, y, z = palette.readwords 5
|
93
|
+
name = nil
|
94
|
+
if @version == 2
|
95
|
+
raise IndexError unless palette.readwords == [ 0 ]
|
96
|
+
len = palette.readwords
|
97
|
+
name = palette.readutf16(len[0] - 1)
|
98
|
+
raise IndexError unless palette.readwords == [ 0 ]
|
99
|
+
end
|
100
|
+
|
101
|
+
color = case space
|
102
|
+
when 0 then # RGB
|
103
|
+
@statistics[:rgb] += 1
|
104
|
+
|
105
|
+
Spectrum::RGB.new(w / 256, x / 256, y / 256)
|
106
|
+
when 1 then # HS[BV] -- Convert to RGB
|
107
|
+
@statistics[:hsb] += 1
|
108
|
+
|
109
|
+
h = w / 65535.0
|
110
|
+
s = x / 65535.0
|
111
|
+
v = y / 65535.0
|
112
|
+
|
113
|
+
if defined?(Spectrum::HSB)
|
114
|
+
Spectrum::HSB.from_fraction(h, s, v)
|
115
|
+
else
|
116
|
+
@statistics[:converted] += 1
|
117
|
+
if Spectrum.near_zero_or_less?(s)
|
118
|
+
Spectrum::RGB.from_fraction(v, v, v)
|
119
|
+
else
|
120
|
+
if Spectrum.near_one_or_more?(h)
|
121
|
+
vh = 0
|
122
|
+
else
|
123
|
+
vh = h * 6.0
|
124
|
+
end
|
125
|
+
|
126
|
+
vi = vh.floor
|
127
|
+
v1 = v.to_f * (1 - s.to_f)
|
128
|
+
v2 = v.to_f * (1 - s.to_f * (vh - vi))
|
129
|
+
v3 = v.to_f * (1 - s.to_f * (1 - (vh - vi)))
|
130
|
+
|
131
|
+
case vi
|
132
|
+
when 0 then Spectrum::RGB.from_fraction(v, v3, v1)
|
133
|
+
when 1 then Spectrum::RGB.from_fraction(v2, v, v1)
|
134
|
+
when 2 then Spectrum::RGB.from_fraction(v1, v, v3)
|
135
|
+
when 3 then Spectrum::RGB.from_fraction(v1, v2, v)
|
136
|
+
when 4 then Spectrum::RGB.from_fraction(v3, v1, v)
|
137
|
+
else Spectrum::RGB.from_fraction(v, v1, v2)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
when 2 then # CMYK
|
142
|
+
@statistics[:cmyk] += 1
|
143
|
+
Spectrum::CMYK.from_percent(100 - (w / 655.35),
|
144
|
+
100 - (x / 655.35),
|
145
|
+
100 - (y / 655.35),
|
146
|
+
100 - (z / 655.35))
|
147
|
+
when 7 then # L*a*b*
|
148
|
+
@statistics[:lab] += 1
|
149
|
+
|
150
|
+
l = [w, 10000].min / 100.0
|
151
|
+
a = [[-12800, UwToSw[x]].max, 12700].min / 100.0
|
152
|
+
b = [[-12800, UwToSw[x]].max, 12700].min / 100.0
|
153
|
+
|
154
|
+
if defined? Spectrum::Lab
|
155
|
+
Spectrum::Lab.new(l, a, b)
|
156
|
+
else
|
157
|
+
[ space, w, x, y, z ]
|
158
|
+
end
|
159
|
+
when 8 then # Grayscale
|
160
|
+
@statistics[:gray] += 1
|
161
|
+
|
162
|
+
g = [w, 10000].min / 100.0
|
163
|
+
Spectrum::GrayScale.new(g)
|
164
|
+
when 9 then # Wide CMYK
|
165
|
+
@statistics[:wcmyk] += 1
|
166
|
+
|
167
|
+
c = [w, 10000].min / 100.0
|
168
|
+
m = [x, 10000].min / 100.0
|
169
|
+
y = [y, 10000].min / 100.0
|
170
|
+
k = [z, 10000].min / 100.0
|
171
|
+
Spectrum::CMYK.from_percent(c, m, y, k)
|
172
|
+
else
|
173
|
+
@statistics[space] += 1
|
174
|
+
[ space, w, x, y, z ]
|
175
|
+
end
|
176
|
+
|
177
|
+
@order << [ color, name ]
|
178
|
+
|
179
|
+
if color.kind_of? Array
|
180
|
+
@lost << color
|
181
|
+
else
|
182
|
+
@colors << color
|
183
|
+
|
184
|
+
if name
|
185
|
+
@names[name] ||= []
|
186
|
+
@names[name] << color
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Provides the colour or colours at the provided selectors.
|
193
|
+
def values_at(*selectors)
|
194
|
+
@colors.values_at(*selectors)
|
195
|
+
end
|
196
|
+
|
197
|
+
# If a Numeric +key+ is provided, the single colour value at that position
|
198
|
+
# will be returned. If a String +key+ is provided, the colour set (an
|
199
|
+
# array) for that colour name will be returned.
|
200
|
+
def [](key)
|
201
|
+
if key.kind_of?(Numeric)
|
202
|
+
@colors[key]
|
203
|
+
else
|
204
|
+
@names[key]
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Loops through each colour.
|
209
|
+
def each
|
210
|
+
@colors.each { |el| yield el }
|
211
|
+
end
|
212
|
+
|
213
|
+
# Loops through each named colour set.
|
214
|
+
def each_name #:yields color_name, color_set:#
|
215
|
+
@names.each { |color_name, color_set| yield color_name, color_set }
|
216
|
+
end
|
217
|
+
|
218
|
+
def size
|
219
|
+
@colors.size
|
220
|
+
end
|
221
|
+
|
222
|
+
attr_reader :version
|
223
|
+
|
224
|
+
def to_aco(version = @version) #:nodoc:
|
225
|
+
res = ""
|
226
|
+
|
227
|
+
res << [ version, @order.size ].pack("nn")
|
228
|
+
|
229
|
+
@order.each do |cnpair|
|
230
|
+
color, name = *cnpair
|
231
|
+
|
232
|
+
# Note: HSB and CMYK formats are lost by the conversions performed on
|
233
|
+
# import. They are turned into RGB and WCMYK, respectively.
|
234
|
+
|
235
|
+
cstr = case color
|
236
|
+
when Array
|
237
|
+
color
|
238
|
+
when Spectrum::RGB
|
239
|
+
r = [(color.red * 256).round, 65535].min
|
240
|
+
g = [(color.green * 256).round, 65535].min
|
241
|
+
b = [(color.blue * 256).round, 65535].min
|
242
|
+
[ 0, r, g, b, 0 ]
|
243
|
+
when Spectrum::GrayScale
|
244
|
+
g = [(color.gray * 100).round, 10000].min
|
245
|
+
[ 8, g, 0, 0, 0 ]
|
246
|
+
when Spectrum::CMYK
|
247
|
+
c = [(color.cyan * 100).round, 10000].min
|
248
|
+
m = [(color.magenta * 100).round, 10000].min
|
249
|
+
y = [(color.yellow * 100).round, 10000].min
|
250
|
+
k = [(color.black * 100).round, 10000].min
|
251
|
+
[ 9, c, m, y, k ]
|
252
|
+
end
|
253
|
+
cstr = cstr.pack("nnnnn")
|
254
|
+
|
255
|
+
nstr = ""
|
256
|
+
|
257
|
+
if version == 2
|
258
|
+
if (name.size / 2 * 2) == name.size # only where s[0] == byte!
|
259
|
+
nstr << [ 0, (name.size / 2) + 1 ].pack("nn")
|
260
|
+
nstr << name
|
261
|
+
nstr << [ 0 ].pack("n")
|
262
|
+
else
|
263
|
+
nstr << [ 0, 1, 0 ].pack("nnn")
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
res << cstr << nstr
|
268
|
+
end
|
269
|
+
|
270
|
+
res
|
271
|
+
end
|
272
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
#--
|
2
|
+
# Color
|
3
|
+
# Colour management with Ruby
|
4
|
+
# http://rubyforge.org/projects/color
|
5
|
+
# Version 1.4.1
|
6
|
+
#
|
7
|
+
# Licensed under a MIT-style licence. See Licence.txt in the main
|
8
|
+
# distribution for full licensing information.
|
9
|
+
#
|
10
|
+
# Copyright (c) 2005 - 2010 Austin Ziegler and Matt Lyon
|
11
|
+
#++
|
12
|
+
|
13
|
+
require 'spectrum/palette'
|
14
|
+
|
15
|
+
# A class that can read a GIMP (GNU Image Manipulation Program) palette file
|
16
|
+
# and provide a Hash-like interface to the contents. GIMP colour palettes
|
17
|
+
# are RGB values only.
|
18
|
+
#
|
19
|
+
# Because two or more entries in a GIMP palette may have the same name, all
|
20
|
+
# named entries are returned as an array.
|
21
|
+
#
|
22
|
+
# pal = Spectrum::Palette::Gimp.from_file(my_gimp_palette)
|
23
|
+
# pal[0] => Spectrum::RGB<...>
|
24
|
+
# pal["white"] => [ Spectrum::RGB<...> ]
|
25
|
+
# pal["unknown"] => [ Spectrum::RGB<...>, Spectrum::RGB<...>, ... ]
|
26
|
+
#
|
27
|
+
# GIMP Palettes are always indexable by insertion order (an integer key).
|
28
|
+
class Spectrum::Palette::Gimp
|
29
|
+
include Enumerable
|
30
|
+
|
31
|
+
class << self
|
32
|
+
# Create a GIMP palette object from the named file.
|
33
|
+
def from_file(filename)
|
34
|
+
File.open(filename, "rb") { |io| Spectrum::Palette::Gimp.from_io(io) }
|
35
|
+
end
|
36
|
+
|
37
|
+
# Create a GIMP palette object from the provided IO.
|
38
|
+
def from_io(io)
|
39
|
+
Spectrum::Palette::Gimp.new(io.read)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Create a new GIMP palette from the palette file as a string.
|
44
|
+
def initialize(palette)
|
45
|
+
@colors = []
|
46
|
+
@names = {}
|
47
|
+
@valid = false
|
48
|
+
@name = "(unnamed)"
|
49
|
+
|
50
|
+
palette.split($/).each do |line|
|
51
|
+
line.chomp!
|
52
|
+
line.gsub!(/\s*#.*\Z/, '')
|
53
|
+
|
54
|
+
next if line.empty?
|
55
|
+
|
56
|
+
if line =~ /\AGIMP Palette\Z/
|
57
|
+
@valid = true
|
58
|
+
next
|
59
|
+
end
|
60
|
+
|
61
|
+
info = /(\w+):\s(.*$)/.match(line)
|
62
|
+
if info
|
63
|
+
@name = info.captures[1] if info.captures[0] =~ /name/i
|
64
|
+
next
|
65
|
+
end
|
66
|
+
|
67
|
+
line.gsub!(/^\s+/, '')
|
68
|
+
data = line.split(/\s+/, 4)
|
69
|
+
name = data.pop.strip
|
70
|
+
data.map! { |el| el.to_i }
|
71
|
+
|
72
|
+
color = Spectrum::RGB.new(*data)
|
73
|
+
|
74
|
+
@colors << color
|
75
|
+
@names[name] ||= []
|
76
|
+
@names[name] << color
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Provides the colour or colours at the provided selectors.
|
81
|
+
def values_at(*selectors)
|
82
|
+
@colors.values_at(*selectors)
|
83
|
+
end
|
84
|
+
|
85
|
+
# If a Numeric +key+ is provided, the single colour value at that position
|
86
|
+
# will be returned. If a String +key+ is provided, the colour set (an
|
87
|
+
# array) for that colour name will be returned.
|
88
|
+
def [](key)
|
89
|
+
if key.kind_of?(Numeric)
|
90
|
+
@colors[key]
|
91
|
+
else
|
92
|
+
@names[key]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Loops through each colour.
|
97
|
+
def each
|
98
|
+
@colors.each { |el| yield el }
|
99
|
+
end
|
100
|
+
|
101
|
+
# Loops through each named colour set.
|
102
|
+
def each_name #:yields color_name, color_set:#
|
103
|
+
@names.each { |color_name, color_set| yield color_name, color_set }
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns true if this is believed to be a valid GIMP palette.
|
107
|
+
def valid?
|
108
|
+
@valid
|
109
|
+
end
|
110
|
+
|
111
|
+
def size
|
112
|
+
@colors.size
|
113
|
+
end
|
114
|
+
|
115
|
+
attr_reader :name
|
116
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
#--
|
2
|
+
# Color
|
3
|
+
# Colour management with Ruby
|
4
|
+
# http://rubyforge.org/projects/color
|
5
|
+
# Version 1.4.1
|
6
|
+
#
|
7
|
+
# Licensed under a MIT-style licence. See Licence.txt in the main
|
8
|
+
# distribution for full licensing information.
|
9
|
+
#
|
10
|
+
# Copyright (c) 2005 - 2010 Austin Ziegler and Matt Lyon
|
11
|
+
#++
|
12
|
+
|
13
|
+
require 'spectrum/palette'
|
14
|
+
|
15
|
+
# Generates a monochromatic constrasting colour palette for background and
|
16
|
+
# foreground. What does this mean?
|
17
|
+
#
|
18
|
+
# Monochromatic: A single colour is used to generate the base palette, and
|
19
|
+
# this colour is lightened five times and darkened five times to provide
|
20
|
+
# eleven distinct colours.
|
21
|
+
#
|
22
|
+
# Contrasting: The foreground is also generated as a monochromatic colour
|
23
|
+
# palette; however, all generated colours are tested to see that they are
|
24
|
+
# appropriately contrasting to ensure maximum readability of the foreground
|
25
|
+
# against the background.
|
26
|
+
class Spectrum::Palette::MonoContrast
|
27
|
+
# Hash of CSS background colour values.
|
28
|
+
#
|
29
|
+
# This is always 11 values:
|
30
|
+
#
|
31
|
+
# 0:: The starting colour.
|
32
|
+
# +1..+5:: Lighter colours.
|
33
|
+
# -1..-5:: Darker colours.
|
34
|
+
attr_reader :background
|
35
|
+
# Hash of CSS foreground colour values.
|
36
|
+
#
|
37
|
+
# This is always 11 values:
|
38
|
+
#
|
39
|
+
# 0:: The starting colour.
|
40
|
+
# +1..+5:: Lighter colours.
|
41
|
+
# -1..-5:: Darker colours.
|
42
|
+
attr_reader :foreground
|
43
|
+
|
44
|
+
DEFAULT_MINIMUM_BRIGHTNESS_DIFF = (125.0 / 255.0)
|
45
|
+
|
46
|
+
# The minimum brightness difference between the background and the
|
47
|
+
# foreground, and must be between 0..1. Setting this value will regenerate
|
48
|
+
# the palette based on the base colours. The default value for this is 125
|
49
|
+
# / 255.0. If this value is set to +nil+, it will be restored to the
|
50
|
+
# default.
|
51
|
+
attr_accessor :minimum_brightness_diff
|
52
|
+
remove_method :minimum_brightness_diff= ;
|
53
|
+
def minimum_brightness_diff=(bd) #:nodoc:
|
54
|
+
if bd.nil?
|
55
|
+
@minimum_brightness_diff = DEFAULT_MINIMUM_BRIGHTNESS_DIFF
|
56
|
+
elsif bd > 1.0
|
57
|
+
@minimum_brightness_diff = 1.0
|
58
|
+
elsif bd < 0.0
|
59
|
+
@minimum_brightness_diff = 0.0
|
60
|
+
else
|
61
|
+
@minimum_brightness_diff = bd
|
62
|
+
end
|
63
|
+
|
64
|
+
regenerate(@background[0], @foreground[0])
|
65
|
+
end
|
66
|
+
|
67
|
+
DEFAULT_MINIMUM_COLOR_DIFF = (500.0 / 255.0)
|
68
|
+
|
69
|
+
# The minimum colour difference between the background and the foreground,
|
70
|
+
# and must be between 0..3. Setting this value will regenerate the palette
|
71
|
+
# based on the base colours. The default value for this is 500 / 255.0.
|
72
|
+
attr_accessor :minimum_color_diff
|
73
|
+
remove_method :minimum_color_diff= ;
|
74
|
+
def minimum_color_diff=(cd) #:noco:
|
75
|
+
if cd.nil?
|
76
|
+
@minimum_color_diff = DEFAULT_MINIMUM_COLOR_DIFF
|
77
|
+
elsif cd > 3.0
|
78
|
+
@minimum_color_diff = 3.0
|
79
|
+
elsif cd < 0.0
|
80
|
+
@minimum_color_diff = 0.0
|
81
|
+
else
|
82
|
+
@minimum_color_diff = cd
|
83
|
+
end
|
84
|
+
regenerate(@background[0], @foreground[0])
|
85
|
+
end
|
86
|
+
|
87
|
+
# Generate the initial palette.
|
88
|
+
def initialize(background, foreground = nil)
|
89
|
+
@minimum_brightness_diff = DEFAULT_MINIMUM_BRIGHTNESS_DIFF
|
90
|
+
@minimum_color_diff = DEFAULT_MINIMUM_COLOR_DIFF
|
91
|
+
|
92
|
+
regenerate(background, foreground)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Generate the colour palettes.
|
96
|
+
def regenerate(background, foreground = nil)
|
97
|
+
foreground ||= background
|
98
|
+
background = background.to_rgb
|
99
|
+
foreground = foreground.to_rgb
|
100
|
+
|
101
|
+
@background = {}
|
102
|
+
@foreground = {}
|
103
|
+
|
104
|
+
@background[-5] = background.darken_by(10)
|
105
|
+
@background[-4] = background.darken_by(25)
|
106
|
+
@background[-3] = background.darken_by(50)
|
107
|
+
@background[-2] = background.darken_by(75)
|
108
|
+
@background[-1] = background.darken_by(85)
|
109
|
+
@background[ 0] = background
|
110
|
+
@background[+1] = background.lighten_by(85)
|
111
|
+
@background[+2] = background.lighten_by(75)
|
112
|
+
@background[+3] = background.lighten_by(50)
|
113
|
+
@background[+4] = background.lighten_by(25)
|
114
|
+
@background[+5] = background.lighten_by(10)
|
115
|
+
|
116
|
+
@foreground[-5] = calculate_foreground(@background[-5], foreground)
|
117
|
+
@foreground[-4] = calculate_foreground(@background[-4], foreground)
|
118
|
+
@foreground[-3] = calculate_foreground(@background[-3], foreground)
|
119
|
+
@foreground[-2] = calculate_foreground(@background[-2], foreground)
|
120
|
+
@foreground[-1] = calculate_foreground(@background[-1], foreground)
|
121
|
+
@foreground[ 0] = calculate_foreground(@background[ 0], foreground)
|
122
|
+
@foreground[+1] = calculate_foreground(@background[+1], foreground)
|
123
|
+
@foreground[+2] = calculate_foreground(@background[+2], foreground)
|
124
|
+
@foreground[+3] = calculate_foreground(@background[+3], foreground)
|
125
|
+
@foreground[+4] = calculate_foreground(@background[+4], foreground)
|
126
|
+
@foreground[+5] = calculate_foreground(@background[+5], foreground)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Given a background colour and a foreground colour, modifies the
|
130
|
+
# foreground colour so that it will have enough contrast to be seen
|
131
|
+
# against the background colour.
|
132
|
+
#
|
133
|
+
# Uses #mininum_brightness_diff and #minimum_color_diff.
|
134
|
+
def calculate_foreground(background, foreground)
|
135
|
+
nfg = nil
|
136
|
+
# Loop through brighter and darker versions of the foreground color. The
|
137
|
+
# numbers here represent the amount of foreground color to mix with
|
138
|
+
# black and white.
|
139
|
+
[100, 75, 50, 25, 0].each do |percent|
|
140
|
+
dfg = foreground.darken_by(percent)
|
141
|
+
lfg = foreground.lighten_by(percent)
|
142
|
+
|
143
|
+
dbd = brightness_diff(background, dfg)
|
144
|
+
lbd = brightness_diff(background, lfg)
|
145
|
+
|
146
|
+
if lbd > dbd
|
147
|
+
nfg = lfg
|
148
|
+
nbd = lbd
|
149
|
+
else
|
150
|
+
nfg = dfg
|
151
|
+
nbd = dbd
|
152
|
+
end
|
153
|
+
|
154
|
+
ncd = color_diff(background, nfg)
|
155
|
+
|
156
|
+
break if nbd >= @minimum_brightness_diff and ncd >= @minimum_color_diff
|
157
|
+
end
|
158
|
+
nfg
|
159
|
+
end
|
160
|
+
|
161
|
+
# Returns the absolute difference between the brightness levels of two
|
162
|
+
# colours. This will be a decimal value between 0 and 1. W3C accessibility
|
163
|
+
# guidelines for colour contrast[http://www.w3.org/TR/AERT#color-contrast]
|
164
|
+
# suggest that this value be at least approximately 0.49 (125 / 255.0) for
|
165
|
+
# proper contrast.
|
166
|
+
def brightness_diff(c1, c2)
|
167
|
+
(c1.brightness - c2.brightness).abs
|
168
|
+
end
|
169
|
+
|
170
|
+
# Returns the contrast between to colours, a decimal value between 0 and
|
171
|
+
# 3. W3C accessibility guidelines for colour
|
172
|
+
# contrast[http://www.w3.org/TR/AERT#color-contrast] suggest that this
|
173
|
+
# value be at least approximately 1.96 (500 / 255.0) for proper contrast.
|
174
|
+
def color_diff(c1, c2)
|
175
|
+
r = (c1.r - c2.r).abs
|
176
|
+
g = (c1.g - c2.g).abs
|
177
|
+
b = (c1.b - c2.b).abs
|
178
|
+
r + g + b
|
179
|
+
end
|
180
|
+
end
|