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.
@@ -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