kmandrup-colorist 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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