kmandrup-colorist 0.1.2
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/.gitignore +5 -0
- data/.specification +66 -0
- data/CHANGELOG.rdoc +10 -0
- data/MIT_LICENSE.rdoc +19 -0
- data/README.rdoc +43 -0
- data/Rakefile +61 -0
- data/VERSION +1 -0
- data/extractions/extract_colors.rb +436 -0
- data/kmandrup-colorist.gemspec +50 -0
- data/lib/colorist/color.rb +415 -0
- data/lib/colorist/color_names.rb +441 -0
- data/lib/colorist/core_extensions.rb +26 -0
- data/lib/kmandrup-colorist.rb +3 -0
- metadata +81 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{kmandrup-colorist}
|
8
|
+
s.version = "0.1.2"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Michael Bleigh", "oleg dashevskii", "Slippy Douglas", "Kristian Mandrup"]
|
12
|
+
s.date = %q{2010-05-27}
|
13
|
+
s.description = %q{Colorist is a library built to handle the easy conversion and manipulation of colors with a special emphasis on W3C standards and CSS-style hex color notation.}
|
14
|
+
s.email = %q{kmandrup@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"MIT_LICENSE.rdoc",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
".specification",
|
22
|
+
"CHANGELOG.rdoc",
|
23
|
+
"MIT_LICENSE.rdoc",
|
24
|
+
"README.rdoc",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"extractions/extract_colors.rb",
|
28
|
+
"kmandrup-colorist.gemspec",
|
29
|
+
"lib/colorist/color.rb",
|
30
|
+
"lib/colorist/color_names.rb",
|
31
|
+
"lib/colorist/core_extensions.rb",
|
32
|
+
"lib/kmandrup-colorist.rb"
|
33
|
+
]
|
34
|
+
s.homepage = %q{http://github.com/kristianmandrup/colorist}
|
35
|
+
s.rdoc_options = ["--main", "README.rdoc"]
|
36
|
+
s.require_paths = ["lib"]
|
37
|
+
s.rubygems_version = %q{1.3.7}
|
38
|
+
s.summary = %q{A library built to handle the easy conversion and simple manipulation of colors.}
|
39
|
+
|
40
|
+
if s.respond_to? :specification_version then
|
41
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
42
|
+
s.specification_version = 3
|
43
|
+
|
44
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
45
|
+
else
|
46
|
+
end
|
47
|
+
else
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
@@ -0,0 +1,415 @@
|
|
1
|
+
module Colorist
|
2
|
+
# Color is the general class for storing and manipulating a color with the
|
3
|
+
# Colorist gem. It provides methods to add, subtract, and calculate aspects
|
4
|
+
# of the color based on W3C and other standards.
|
5
|
+
class Color
|
6
|
+
attr_accessor :r, :g, :b, :a
|
7
|
+
|
8
|
+
CSS_COLOR_NAMES = { "maroon" => 0x800000,
|
9
|
+
"red" => 0xff0000,
|
10
|
+
"orange" => 0xffa500,
|
11
|
+
"yellow" => 0xffff00,
|
12
|
+
"olive" => 0x808000,
|
13
|
+
"purple" => 0x800080,
|
14
|
+
"fuchsia" => 0xff00ff,
|
15
|
+
"white" => 0xffffff,
|
16
|
+
"lime" => 0x00ff00,
|
17
|
+
"green" => 0x008000,
|
18
|
+
"navy" => 0x000080,
|
19
|
+
"blue" => 0x0000ff,
|
20
|
+
"aqua" => 0x00ffff,
|
21
|
+
"teal" => 0x008080,
|
22
|
+
"black" => 0x000000,
|
23
|
+
"silver" => 0xc0c0c0,
|
24
|
+
"gray" => 0x808080 }
|
25
|
+
|
26
|
+
# Creates a new color with the hex color provided as a number (i.e. 0x112233)
|
27
|
+
def initialize(color = 0x000000, alpha = 1.0)
|
28
|
+
string = "%.6x" % color
|
29
|
+
@r = string[0..1].hex
|
30
|
+
@g = string[2..3].hex
|
31
|
+
@b = string[4..5].hex
|
32
|
+
@a = alpha
|
33
|
+
end
|
34
|
+
|
35
|
+
# Initialize a color based on RGB values. By default, the values
|
36
|
+
# should be between 0 and 255. If you use the option <tt>:percent => true</tt>,
|
37
|
+
# the values should then be between 0.0 and 1.0.
|
38
|
+
def self.from_rgb(r, g, b, options = {})
|
39
|
+
color = new
|
40
|
+
# convert from 0.0 to 1.0 to 0 to 255 if the :percent option is used
|
41
|
+
if options[:percent]
|
42
|
+
color.r, color.g, color.b = (r * 255).round, (g * 255).round, (b * 255).round
|
43
|
+
else
|
44
|
+
color.r, color.g, color.b = r, g, b
|
45
|
+
end
|
46
|
+
color
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.from_rgba(r, g, b, a, options = {})
|
50
|
+
color = from_rgb(r, g, b, options)
|
51
|
+
color.a = a
|
52
|
+
color
|
53
|
+
end
|
54
|
+
|
55
|
+
# Initialize a colour based on HSV/HSB values. Hue should be between 0 and 360 (inclusive),
|
56
|
+
# while saturation and value should be from 0.0 to 1.0.
|
57
|
+
def self.from_hsv(hue, saturation, value)
|
58
|
+
saturation = 1 if saturation > 1
|
59
|
+
value = 1 if saturation > 1
|
60
|
+
|
61
|
+
# Conversion formula taken from wikipedia
|
62
|
+
|
63
|
+
f = (hue / 60.0) - (hue / 60).floor
|
64
|
+
|
65
|
+
p = value * (1 - saturation)
|
66
|
+
q = value * (1 - (saturation * f))
|
67
|
+
t = value * (1 - (saturation * (1 - f)))
|
68
|
+
|
69
|
+
r, g, b = case (hue / 60).floor % 6
|
70
|
+
when 0 then [ value, t, p ]
|
71
|
+
when 1 then [ q, value, p ]
|
72
|
+
when 2 then [ p, value, t ]
|
73
|
+
when 3 then [ p, q, value ]
|
74
|
+
when 4 then [ t, p, value ]
|
75
|
+
when 5 then [ value, p, q ]
|
76
|
+
end
|
77
|
+
|
78
|
+
from_rgb(r, g, b, :percent => true)
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.from_hsva(hue, saturation, value, alpha)
|
82
|
+
color = from_hsv(hue, saturation, value)
|
83
|
+
color.a = alpha
|
84
|
+
color
|
85
|
+
end
|
86
|
+
|
87
|
+
# Converts a CSS hex string into a color. Works both with the
|
88
|
+
# full form (i.e. <tt>#ffffff</tt>) and the abbreviated form (<tt>#fff</tt>). Can
|
89
|
+
# also take any of the 16 named CSS colors.
|
90
|
+
def self.from_string(some_string)
|
91
|
+
if matched = some_string.match(/\A#([0-9a-f]{3})\z/i)
|
92
|
+
color = from_rgb(*matched[1].split(//).collect{|v| "#{v}#{v}".hex })
|
93
|
+
elsif matched = some_string.match(/\A#([0-9a-f]{6})\z/i)
|
94
|
+
color = new
|
95
|
+
color.r = matched[1][0..1].hex
|
96
|
+
color.g = matched[1][2..3].hex
|
97
|
+
color.b = matched[1][4..5].hex
|
98
|
+
elsif CSS_COLOR_NAMES.key?(some_string)
|
99
|
+
color = new(CSS_COLOR_NAMES[some_string])
|
100
|
+
else
|
101
|
+
raise ArgumentError, "Must provide a valid CSS hex color or color name.", caller
|
102
|
+
end
|
103
|
+
color
|
104
|
+
end
|
105
|
+
|
106
|
+
# Create a new color from the provided object. Duplicates Color objects
|
107
|
+
# and attempts to call <tt>to_color</tt> on other objects. Will raise
|
108
|
+
# an ArgumentError if it is unable to coerce the color.
|
109
|
+
def self.from(some_entity)
|
110
|
+
case some_entity
|
111
|
+
when Colorist::Color
|
112
|
+
some_entity.dup
|
113
|
+
else
|
114
|
+
raise ArgumentError, "#{some_entity.class.to_s} cannot be coerced into a color.", caller unless some_entity.respond_to?(:to_color)
|
115
|
+
some_entity.to_color
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Create a duplicate of this color.
|
120
|
+
def dup
|
121
|
+
self.class.from_rgba(@r, @g, @b, @a)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Add the individual RGB values of two colors together.
|
125
|
+
# You may also use an equivalent numeric or string color representation.
|
126
|
+
# The alpha value is kept from the original color (left-hand side).
|
127
|
+
#
|
128
|
+
# Examples:
|
129
|
+
#
|
130
|
+
# gray = Colorist::Color.new(0x333333)
|
131
|
+
# gray + "#300" # => <Color #663333>
|
132
|
+
# gray + 0x000000 # => <Color #333333>
|
133
|
+
# white = "white".to_color
|
134
|
+
# gray + white # => <Color #ffffff>
|
135
|
+
def +(other_color)
|
136
|
+
other_color = self.class.from(other_color)
|
137
|
+
color = self.dup
|
138
|
+
color.r += other_color.r
|
139
|
+
color.g += other_color.g
|
140
|
+
color.b += other_color.b
|
141
|
+
color
|
142
|
+
end
|
143
|
+
|
144
|
+
# Subtract the individual RGB values of the two colors together.
|
145
|
+
# You may also use an equivalent numeric or string color representation.
|
146
|
+
# The alpha value is kept from the original color (left-hand side).
|
147
|
+
def -(other_color)
|
148
|
+
other_color = self.class.from(other_color)
|
149
|
+
color = self.dup
|
150
|
+
color.r -= other_color.r
|
151
|
+
color.g -= other_color.g
|
152
|
+
color.b -= other_color.b
|
153
|
+
color
|
154
|
+
end
|
155
|
+
|
156
|
+
# Multiply the individual RGB values of two colors together or against a Float.
|
157
|
+
# Colors divided in a way that is equivalent to each of the values being normalized to 0.0..1.0 prior to the operation
|
158
|
+
# and normalized back to 0.0..255.0 after the operation.
|
159
|
+
# You may also use an equivalent numeric or string color representation.
|
160
|
+
# The alpha value is kept from the original color (left-hand side).
|
161
|
+
def *(other)
|
162
|
+
color = self.dup
|
163
|
+
|
164
|
+
if other.is_a? Float
|
165
|
+
color.r *= other
|
166
|
+
color.g *= other
|
167
|
+
color.b *= other
|
168
|
+
else
|
169
|
+
other_color = self.class.from(other)
|
170
|
+
color.r = color.r * other_color.r / 255.0
|
171
|
+
color.g = color.g * other_color.g / 255.0
|
172
|
+
color.b = color.b * other_color.b / 255.0
|
173
|
+
end
|
174
|
+
|
175
|
+
color
|
176
|
+
end
|
177
|
+
|
178
|
+
# Divide the individual RGB values of the two colors together or against a Float.
|
179
|
+
# Colors divided in a way that is equivalent to each of the values being normalized to 0.0..1.0 prior to the operation
|
180
|
+
# and normalized back to 0.0..255.0 after the operation.
|
181
|
+
# You may also use an equivalent numeric or string color representation.
|
182
|
+
# The alpha value is kept from the original color (left-hand side).
|
183
|
+
def /(other)
|
184
|
+
color = self.dup
|
185
|
+
|
186
|
+
if other.is_a? Float
|
187
|
+
color.r /= other
|
188
|
+
color.g /= other
|
189
|
+
color.b /= other
|
190
|
+
else
|
191
|
+
other_color = self.class.from(other)
|
192
|
+
color.r = color.r / other_color.r * 255.0
|
193
|
+
color.g = color.g / other_color.g * 255.0
|
194
|
+
color.b = color.b / other_color.b * 255.0
|
195
|
+
end
|
196
|
+
|
197
|
+
color
|
198
|
+
end
|
199
|
+
|
200
|
+
# Compares colors based on brightness.
|
201
|
+
def <=>(other_color)
|
202
|
+
other_color = self.class.from(other_color)
|
203
|
+
brightness <=> other_color.brightness
|
204
|
+
end
|
205
|
+
|
206
|
+
# Compares colors based on brightness.
|
207
|
+
def < (other_color)
|
208
|
+
other_color = self.class.from(other_color)
|
209
|
+
brightness < other_color.brightness
|
210
|
+
end
|
211
|
+
|
212
|
+
# Compares colors based on brightness.
|
213
|
+
def > (other_color)
|
214
|
+
other_color = self.class.from(other_color)
|
215
|
+
brightness > other_color.brightness
|
216
|
+
end
|
217
|
+
|
218
|
+
# Equal if the red, green, blue, and alpha values are identical.
|
219
|
+
def ==(other_color)
|
220
|
+
other_color = self.class.from(other_color)
|
221
|
+
other_color.r == self.r && other_color.g == self.g && other_color.b == self.b && other_color.a == self.a
|
222
|
+
end
|
223
|
+
|
224
|
+
# Equal if the brightnesses of the two colors are identical.
|
225
|
+
def ===(other_color)
|
226
|
+
other_color = self.class.from(other_color)
|
227
|
+
other_color.brightness == brightness
|
228
|
+
end
|
229
|
+
|
230
|
+
def r=(value) #:nodoc:
|
231
|
+
@r = value; normalize; end
|
232
|
+
def g=(value) #:nodoc:
|
233
|
+
@g = value; normalize; end
|
234
|
+
def b=(value) #:nodoc:
|
235
|
+
@b = value; normalize; end
|
236
|
+
def a=(value) #:nodoc:
|
237
|
+
@a = value; normalize; end
|
238
|
+
|
239
|
+
# Outputs a string representation of the color in the desired format.
|
240
|
+
# The available formats are:
|
241
|
+
#
|
242
|
+
# * <tt>:css</tt> - As a CSS hex string (i.e. <tt>#ffffff</tt>) (default)
|
243
|
+
# * <tt>:css_rgb</tt> - As a CSS RGB value string (i.e. <tt>rgb(255, 255, 255)</tt>)
|
244
|
+
# * <tt>:css_rgba</tt> - As a CSS RGBA value string (i.e. <tt>rgb(255, 255, 255, 1.0)</tt>)
|
245
|
+
# * <tt>:rgb</tt> - As an RGB triplet (i.e. <tt>1.0, 1.0, 1.0</tt>)
|
246
|
+
# * <tt>:rgba</tt> - As an RGBA quadruplet (i.e. <tt>1.0, 1.0, 1.0, 1.0</tt>)
|
247
|
+
def to_s(format=:css)
|
248
|
+
case format
|
249
|
+
when :css
|
250
|
+
"#%.2x%.2x%.2x" % [r, g, b]
|
251
|
+
when :css_rgb
|
252
|
+
"rgb(%d, %d, %d)" % [r, g, b]
|
253
|
+
when :css_rgba
|
254
|
+
"rgba(%d, %d, %d, %.3f)" % [r, g, b, a]
|
255
|
+
when :rgb
|
256
|
+
"%.3f, %.3f, %.3f" % [r / 255, g / 255, b / 255]
|
257
|
+
when :rgba
|
258
|
+
"%.3f, %.3f, %.3f, %.3f" % [r / 255, g / 255, b / 255, a / 255]
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# Returns an array of the hue, saturation and value of the color.
|
263
|
+
# Hue will range from 0-359, hue and saturation will be between 0 and 1.
|
264
|
+
|
265
|
+
def to_hsv
|
266
|
+
red, green, blue = *[r, g, b].collect {|x| x / 255.0}
|
267
|
+
max = [red, green, blue].max
|
268
|
+
min = [red, green, blue].min
|
269
|
+
|
270
|
+
if min == max
|
271
|
+
hue = 0
|
272
|
+
elsif max == red
|
273
|
+
hue = 60 * ((green - blue) / (max - min))
|
274
|
+
elsif max == green
|
275
|
+
hue = 60 * ((blue - red) / (max - min)) + 120
|
276
|
+
elsif max == blue
|
277
|
+
hue = 60 * ((red - green) / (max - min)) + 240
|
278
|
+
end
|
279
|
+
|
280
|
+
saturation = (max == 0) ? 0 : (max - min) / max
|
281
|
+
[hue % 360, saturation, max]
|
282
|
+
end
|
283
|
+
|
284
|
+
def inspect
|
285
|
+
"#<Color #{to_s(:css_rgba)}>"
|
286
|
+
end
|
287
|
+
|
288
|
+
# Returns the perceived brightness of the provided color on a
|
289
|
+
# scale of 0.0 to 1.0 based on the formula provided. The formulas
|
290
|
+
# available are:
|
291
|
+
#
|
292
|
+
# * <tt>:w3c</tt> - <tt>((r * 299 + g * 587 + b * 114) / 1000 / 255</tt>
|
293
|
+
# * <tt>:standard</tt> - <tt>sqrt(0.241 * r^2 + 0.691 * g^2 + 0.068 * b^2) / 255</tt>
|
294
|
+
def brightness(formula = :w3c)
|
295
|
+
case formula
|
296
|
+
when :standard
|
297
|
+
Math.sqrt(0.241 * r**2 + 0.691 * g**2 + 0.068 * b**2) / 255
|
298
|
+
when :w3c
|
299
|
+
((r * 299 + g * 587 + b * 114) / 255000.0)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
# Contrast this color with another color using the provided formula. The
|
304
|
+
# available formulas are:
|
305
|
+
#
|
306
|
+
# * <tt>:w3c</tt> - <tt>(max(r1 r2) - min(r1 r2)) + (max(g1 g2) - min(g1 g2)) + (max(b1 b2) - min(b1 b2))</tt>
|
307
|
+
def contrast_with(other_color, formula=:w3c)
|
308
|
+
other_color = self.class.from(other_color)
|
309
|
+
case formula
|
310
|
+
when :w3c
|
311
|
+
(([self.r, other_color.r].max - [self.r, other_color.r].min) +
|
312
|
+
([self.g, other_color.g].max - [self.g, other_color.g].min) +
|
313
|
+
([self.b, other_color.b].max - [self.b, other_color.b].min)) / 765.0
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# Returns the opposite of the current color.
|
318
|
+
def invert
|
319
|
+
self.class.from_rgba(255 - r, 255 - g, 255 - b, a)
|
320
|
+
end
|
321
|
+
|
322
|
+
alias opposite invert
|
323
|
+
|
324
|
+
# Uses a naive formula to generate a gradient between this color and the given color.
|
325
|
+
# Returns the array of colors that make the gradient, including this color and the
|
326
|
+
# target color. By default will return 10 colors, but this can be changed by supplying
|
327
|
+
# an optional steps parameter.
|
328
|
+
def gradient_to(color, steps = 10)
|
329
|
+
color_to = self.class.from(color)
|
330
|
+
red = color_to.r - r
|
331
|
+
green = color_to.g - g
|
332
|
+
blue = color_to.b - b
|
333
|
+
|
334
|
+
result = (1..(steps - 3)).to_a.collect do |step|
|
335
|
+
percentage = step.to_f / (steps - 1)
|
336
|
+
self.class.from_rgb(r + (red * percentage), g + (green * percentage), b + (blue * percentage))
|
337
|
+
end
|
338
|
+
|
339
|
+
# Add first and last colors to result, avoiding uneccessary calculation and rounding errors
|
340
|
+
|
341
|
+
result.unshift(self.dup)
|
342
|
+
result.push(color.dup)
|
343
|
+
result
|
344
|
+
end
|
345
|
+
|
346
|
+
# Converts the current color to grayscale using the brightness
|
347
|
+
# formula provided. See #brightness for a description of the
|
348
|
+
# available formulas.
|
349
|
+
def to_grayscale(formula = :w3c)
|
350
|
+
b = brightness(formula)
|
351
|
+
self.class.from_rgb(255 * b, 255 * b, 255 * b)
|
352
|
+
end
|
353
|
+
|
354
|
+
# Returns an appropriate text color (either black or white) based on
|
355
|
+
# the brightness of this color. The +threshold+ specifies the brightness
|
356
|
+
# cutoff point.
|
357
|
+
def text_color(threshold = 0.6, formula = :standard)
|
358
|
+
brightness(formula) > threshold ? self.class.new(0x000000) : self.class.new(0xffffff)
|
359
|
+
end
|
360
|
+
|
361
|
+
# Adjusts any of H, S, V values with relative values: +opts[:h]+, +opts[:s]+, +opts[:v]+
|
362
|
+
# and returns adjusted value.
|
363
|
+
def adjust(opts = {})
|
364
|
+
unless [:h, :s, :v].any? { |part| opts.include? part }
|
365
|
+
raise ArgumentError, "please specify at least one of :h, :s, or :v options"
|
366
|
+
end
|
367
|
+
|
368
|
+
h, s, v = *self.to_hsv
|
369
|
+
|
370
|
+
h = (h + opts[:h]) % 360 if opts[:h]
|
371
|
+
s = _clamp(s + opts[:s], 0..1) if opts[:s]
|
372
|
+
v = _clamp(v + opts[:v], 0..1) if opts[:v]
|
373
|
+
|
374
|
+
self.class.from_hsv(h, s, v)
|
375
|
+
end
|
376
|
+
|
377
|
+
# Adjusts any of R, G, B, or A values with aboslute values: opts[:a], opts[:r], opts[:b], opts[:a]
|
378
|
+
# and returns adjusted value.
|
379
|
+
def with(opts = {})
|
380
|
+
unless [:r, :g, :b, :a].any? { |part| opts.include? part }
|
381
|
+
raise ArgumentError, "please specify at least one of :r, :g, :b, or :a options"
|
382
|
+
end
|
383
|
+
|
384
|
+
color = self.dup
|
385
|
+
|
386
|
+
color.r = opts[:r] if opts[:r]
|
387
|
+
color.g = opts[:g] if opts[:g]
|
388
|
+
color.b = opts[:b] if opts[:b]
|
389
|
+
color.a = opts[:a] if opts[:a]
|
390
|
+
|
391
|
+
color
|
392
|
+
end
|
393
|
+
|
394
|
+
protected
|
395
|
+
|
396
|
+
def normalize #:nodoc:
|
397
|
+
@r = _clamp(@r, (0..255)) unless (0..255).include? @r
|
398
|
+
@g = _clamp(@g, (0..255)) unless (0..255).include? @g
|
399
|
+
@b = _clamp(@b, (0..255)) unless (0..255).include? @b
|
400
|
+
@a = _clamp(@a, (0.0..1.0)) unless (0.0..1.0).include? @a
|
401
|
+
end
|
402
|
+
|
403
|
+
def _clamp(value, range)
|
404
|
+
if value < range.first
|
405
|
+
range.first
|
406
|
+
elsif value > range.last
|
407
|
+
range.last
|
408
|
+
else
|
409
|
+
value
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
end # Color
|
414
|
+
|
415
|
+
end # Colorist
|