color 1.7.1 → 2.0.0.pre.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.
Files changed (53) hide show
  1. checksums.yaml +5 -13
  2. data/CHANGELOG.md +298 -0
  3. data/CODE_OF_CONDUCT.md +128 -0
  4. data/CONTRIBUTING.md +70 -0
  5. data/CONTRIBUTORS.md +10 -0
  6. data/LICENCE.md +27 -0
  7. data/Manifest.txt +11 -21
  8. data/README.md +54 -0
  9. data/Rakefile +74 -53
  10. data/SECURITY.md +34 -0
  11. data/lib/color/cielab.rb +348 -0
  12. data/lib/color/cmyk.rb +279 -213
  13. data/lib/color/grayscale.rb +128 -160
  14. data/lib/color/hsl.rb +205 -173
  15. data/lib/color/rgb/colors.rb +177 -163
  16. data/lib/color/rgb.rb +534 -537
  17. data/lib/color/version.rb +5 -0
  18. data/lib/color/xyz.rb +214 -0
  19. data/lib/color/yiq.rb +91 -46
  20. data/lib/color.rb +208 -141
  21. data/test/fixtures/cielab.json +444 -0
  22. data/test/minitest_helper.rb +20 -4
  23. data/test/test_cmyk.rb +49 -71
  24. data/test/test_color.rb +58 -106
  25. data/test/test_grayscale.rb +35 -56
  26. data/test/test_hsl.rb +72 -76
  27. data/test/test_rgb.rb +195 -267
  28. data/test/test_yiq.rb +12 -30
  29. metadata +165 -150
  30. checksums.yaml.gz.sig +0 -0
  31. data/.autotest +0 -5
  32. data/.gemtest +0 -0
  33. data/.hoerc +0 -2
  34. data/.minitest.rb +0 -2
  35. data/.travis.yml +0 -35
  36. data/Contributing.rdoc +0 -60
  37. data/Gemfile +0 -9
  38. data/History.rdoc +0 -172
  39. data/Licence.rdoc +0 -27
  40. data/README.rdoc +0 -50
  41. data/lib/color/css.rb +0 -7
  42. data/lib/color/palette/adobecolor.rb +0 -260
  43. data/lib/color/palette/gimp.rb +0 -104
  44. data/lib/color/palette/monocontrast.rb +0 -164
  45. data/lib/color/palette.rb +0 -4
  46. data/lib/color/rgb/contrast.rb +0 -57
  47. data/lib/color/rgb/metallic.rb +0 -28
  48. data/test/test_adobecolor.rb +0 -405
  49. data/test/test_css.rb +0 -19
  50. data/test/test_gimp.rb +0 -87
  51. data/test/test_monocontrast.rb +0 -130
  52. data.tar.gz.sig +0 -0
  53. metadata.gz.sig +0 -0
data/lib/color/hsl.rb CHANGED
@@ -1,237 +1,269 @@
1
- # -*- ruby encoding: utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
- # An HSL colour object. Internally, the hue (#h), saturation (#s), and
4
- # luminosity/lightness (#l) values are dealt with as fractional values in
5
- # the range 0..1.
3
+ ##
4
+ # The \HSL color model is a cylindrical-coordinate representation of the sRGB color model,
5
+ # standing for hue (measured in degrees on the cylinder), saturation (measured in
6
+ # percentage), and lightness (measured in percentage).
7
+ #
8
+ # \HSL colors are immutable Data class instances. Array deconstruction is `[hue,
9
+ # saturation, luminosity]` and hash deconstruction is `{h:, hue:, s:, saturation:, l:,
10
+ # luminosity:}`. See #h, #hue, #s, #saturation, #l, #luminosity.
6
11
  class Color::HSL
7
12
  include Color
8
13
 
9
- class << self
10
- # Creates an HSL colour object from fractional values 0..1.
11
- def from_fraction(h = 0.0, s = 0.0, l = 0.0, &block)
12
- new(h, s, l, 1.0, 1.0, &block)
13
- end
14
- end
14
+ ##
15
+ # :attr_reader: h
16
+ # Returns the hue of the color in the range 0.0..1.0.
15
17
 
16
- # Coerces the other Color object into HSL.
17
- def coerce(other)
18
- other.to_hsl
19
- end
18
+ ##
19
+ # :attr_reader: hue
20
+ # Returns the hue of the color in degrees (0.0..360.0).
20
21
 
21
- # Creates an HSL colour object from the standard values of degrees and
22
- # percentages (e.g., 145 deg, 30%, 50%).
23
- def initialize(h = 0, s = 0, l = 0, radix1 = 360.0, radix2 = 100.0, &block) # :yields self:
24
- @h = Color.normalize(h / radix1)
25
- @s = Color.normalize(s / radix2)
26
- @l = Color.normalize(l / radix2)
27
- block.call if block
28
- end
22
+ ##
23
+ # :attr_reader: s
24
+ # Returns the saturation of the color in the range 0.0..1.0.
29
25
 
30
- # Present the colour as an HTML/CSS colour string.
31
- def html
32
- to_rgb.html
33
- end
26
+ ##
27
+ # :attr_reader: saturation
28
+ # Returns the percentage of saturation of the color (0.0..100.0).
34
29
 
35
- # Present the colour as an RGB HTML/CSS colour string (e.g., "rgb(0%, 50%,
36
- # 100%)"). Note that this will perform a #to_rgb operation using the
37
- # default conversion formula.
38
- def css_rgb
39
- to_rgb.css_rgb
40
- end
30
+ ##
31
+ # :attr_reader: brightness
32
+ # Returns the luminosity (#l) of the color.
41
33
 
42
- # Present the colour as an RGBA (with alpha) HTML/CSS colour string (e.g.,
43
- # "rgb(0%, 50%, 100%, 1)"). Note that this will perform a #to_rgb
44
- # operation using the default conversion formula.
45
- def css_rgba
46
- to_rgb.css_rgba
34
+ ##
35
+ # :attr_reader: l
36
+ # Returns the luminosity of the color in the range 0.0..1.0.
37
+
38
+ ##
39
+ # :attr_reader: luminosity
40
+ # Returns the percentage of luminosity of the color.
41
+
42
+ ##
43
+ # Creates a \HSL color object from degrees (0.0 .. 360.0) and percentage values
44
+ # (0.0 .. 100.0).
45
+ #
46
+ # ```ruby
47
+ # Color::HSL.from_values(145, 30, 50) # => HSL [145deg 30% 50%]
48
+ # Color::HSL.from_values(h: 145, s: 30, l: 50) # => HSL [145deg 30% 50%]
49
+ # ```
50
+ #
51
+ # :call-seq:
52
+ # from_values(h, s, l)
53
+ # from_values(h:, s:, l:)
54
+ def self.from_values(*args, **kwargs)
55
+ h, s, l =
56
+ case [args, kwargs]
57
+ in [[_, _, _], {}]
58
+ args
59
+ in [[], {h:, s:, l:}]
60
+ [h, s, l]
61
+ else
62
+ new(*args, **kwargs)
63
+ end
64
+
65
+ new(h: h / 360.0, s: s / 100.0, l: l / 100.0)
47
66
  end
48
67
 
49
- # Present the colour as an HSL HTML/CSS colour string (e.g., "hsl(180,
50
- # 25%, 35%)").
51
- def css_hsl
52
- "hsl(%3.2f, %3.2f%%, %3.2f%%)" % [ hue, saturation, luminosity ]
68
+ class << self
69
+ alias_method :from_fraction, :new
70
+ alias_method :from_internal, :new # :nodoc:
53
71
  end
54
72
 
55
- # Present the colour as an HSLA (with alpha) HTML/CSS colour string (e.g.,
56
- # "hsla(180, 25%, 35%, 1)").
57
- def css_hsla
58
- "hsla(%3.2f, %3.2f%%, %3.2f%%, %3.2f)" % [ hue, saturation, luminosity, 1 ]
73
+ ##
74
+ # Creates a \HSL color object from fractional values (0.0 .. 1.0).
75
+ #
76
+ # ```ruby
77
+ # Color::HSL.from_fraction(0.3, 0.3, 0.5) # => HSL [108deg 30% 50%]
78
+ # Color::HSL.new(h: 0.3, s: 0.3, l: 0.5) # => HSL [108deg 30% 50%]
79
+ # Color::HSL[0.3, 0.3, 0.5] # => HSL [108deg 30% 50%]
80
+ # ```
81
+ def initialize(h:, s:, l:)
82
+ super(h: normalize(h), s: normalize(s), l: normalize(l))
59
83
  end
60
84
 
61
- # Converting from HSL to RGB. As with all colour conversions, this is
62
- # approximate at best. The code here is adapted from fvd and van Dam,
63
- # originally found at [1] (implemented similarly at [2]).
85
+ ##
86
+ # Coerces the other Color object into \HSL.
87
+ def coerce(other) = other.to_hsl
88
+
89
+ ##
90
+ # Converts from \HSL to Color::RGB.
91
+ #
92
+ # As with all color conversions, this is an approximation. The code here is adapted from
93
+ # fvd and van Dam, originally found at [1] (implemented similarly at [2]). This
94
+ # simplifies the calculations with the following assumptions:
64
95
  #
65
- # This simplifies the calculations with the following assumptions:
66
96
  # - Luminance values <= 0 always translate to Color::RGB::Black.
67
97
  # - Luminance values >= 1 always translate to Color::RGB::White.
68
- # - Saturation values <= 0 always translate to a shade of gray using
69
- # luminance as a percentage of gray.
98
+ # - Saturation values <= 0 always translate to a shade of gray using luminance as
99
+ # a percentage of gray.
70
100
  #
71
101
  # [1] http://bobpowell.net/RGBHSB.aspx
72
102
  # [2] http://support.microsoft.com/kb/29240
73
- def to_rgb(*)
74
- if Color.near_zero_or_less?(l)
103
+ def to_rgb(...)
104
+ if near_zero_or_less?(l)
75
105
  Color::RGB::Black
76
- elsif Color.near_one_or_more?(l)
106
+ elsif near_one_or_more?(l)
77
107
  Color::RGB::White
78
- elsif Color.near_zero?(s)
79
- Color::RGB.from_grayscale_fraction(l)
108
+ elsif near_zero?(s)
109
+ Color::RGB.from_fraction(l, l, l)
80
110
  else
81
- # Only needed for Ruby 1.8. For Ruby 1.9+, we can do:
82
- # Color::RGB.new(*compute_fvd_rgb, 1.0)
83
- Color::RGB.new(*(compute_fvd_rgb + [ 1.0 ]))
111
+ Color::RGB.from_fraction(*compute_fvd_rgb)
84
112
  end
85
113
  end
86
114
 
87
- # Converts to RGB then YIQ.
88
- def to_yiq
89
- to_rgb.to_yiq
90
- end
115
+ ##
116
+ # Converts from \HSL to Color::YIQ via Color::RGB.
117
+ def to_yiq(...) = to_rgb(...).to_yiq(...)
91
118
 
92
- # Converts to RGB then CMYK.
93
- def to_cmyk
94
- to_rgb.to_cmyk
95
- end
119
+ ##
120
+ # Converts from \HSL to Color::CMYK via Color::RGB.
121
+ def to_cmyk(...) = to_rgb(...).to_cmyk(...)
96
122
 
97
- # Returns the luminosity (#l) of the colour.
98
- def brightness
99
- @l
100
- end
101
- def to_greyscale
102
- Color::GrayScale.from_fraction(@l)
103
- end
104
- alias to_grayscale to_greyscale
123
+ ##
124
+ # Converts from \HSL to Color::Grayscale.
125
+ #
126
+ # Luminance is treated as the Grayscale ratio.
127
+ def to_grayscale(...) = Color::Grayscale.from_fraction(l)
105
128
 
106
- # Returns the hue of the colour in degrees.
107
- def hue
108
- @h * 360.0
109
- end
110
- # Returns the hue of the colour in the range 0.0 .. 1.0.
111
- def h
112
- @h
113
- end
114
- # Sets the hue of the colour in degrees. Colour is perceived as a wheel,
115
- # so values should be set properly even with negative degree values.
116
- def hue=(hh)
117
- hh = hh / 360.0
129
+ ##
130
+ # Converts from \HSL to Color::CIELAB via Color::RGB.
131
+ def to_lab(...) = to_rgb(...).to_lab(...)
118
132
 
119
- hh += 1.0 if hh < 0.0
120
- hh -= 1.0 if hh > 1.0
133
+ ##
134
+ # Converts from \HSL to Color::XYZ via Color::RGB.
135
+ def to_xyz(...) = to_rgb(...).to_xyz(...)
121
136
 
122
- @h = Color.normalize(hh)
123
- end
124
- # Sets the hue of the colour in the range 0.0 .. 1.0.
125
- def h=(hh)
126
- @h = Color.normalize(hh)
127
- end
128
- # Returns the percentage of saturation of the colour.
129
- def saturation
130
- @s * 100.0
131
- end
132
- # Returns the saturation of the colour in the range 0.0 .. 1.0.
133
- def s
134
- @s
135
- end
136
- # Sets the percentage of saturation of the colour.
137
- def saturation=(ss)
138
- @s = Color.normalize(ss / 100.0)
139
- end
140
- # Sets the saturation of the colour in the ragne 0.0 .. 1.0.
141
- def s=(ss)
142
- @s = Color.normalize(ss)
143
- end
137
+ ##
138
+ def to_hsl(...) = self
144
139
 
145
- # Returns the percentage of luminosity of the colour.
146
- def luminosity
147
- @l * 100.0
148
- end
149
- alias lightness luminosity
150
- # Returns the luminosity of the colour in the range 0.0 .. 1.0.
151
- def l
152
- @l
153
- end
154
- # Sets the percentage of luminosity of the colour.
155
- def luminosity=(ll)
156
- @l = Color.normalize(ll / 100.0)
157
- end
158
- alias lightness= luminosity= ;
159
- # Sets the luminosity of the colour in the ragne 0.0 .. 1.0.
160
- def l=(ll)
161
- @l = Color.normalize(ll)
162
- end
140
+ ##
141
+ # Present the color as a CSS `hsl` function with optional `alpha`.
142
+ #
143
+ # ```ruby
144
+ # hsl = Color::HSL.from_values(145, 30, 50)
145
+ # hsl.css # => hsl(145deg 30% 50%)
146
+ # hsl.css(alpha: 0.5) # => hsl(145deg 30% 50% / 0.50)
147
+ # ```
148
+ def css(alpha: nil)
149
+ params = [
150
+ css_value(hue, :degrees),
151
+ css_value(saturation, :percent),
152
+ css_value(luminosity, :percent)
153
+ ].join(" ")
154
+ params = "#{params} / #{css_value(alpha)}" if alpha
163
155
 
164
- def to_hsl
165
- self
156
+ "hsl(#{params})"
166
157
  end
167
158
 
168
- def inspect
169
- "HSL [%.2f deg, %.2f%%, %.2f%%]" % [ hue, saturation, luminosity ]
159
+ ##
160
+ def brightness = l # :nodoc:
161
+
162
+ ##
163
+ def hue = h * 360.0 # :nodoc:
164
+
165
+ ##
166
+ def saturation = s * 100.0 # :nodoc:
167
+
168
+ ##
169
+ def luminosity = l * 100.0 # :nodoc:
170
+
171
+ ##
172
+ alias_method :lightness, :luminosity
173
+
174
+ ##
175
+ def inspect = "HSL [%.2fdeg %.2f%% %.2f%%]" % [hue, saturation, luminosity] # :nodoc:
176
+
177
+ ##
178
+ def pretty_print(q) # :nodoc:
179
+ q.text "HSL"
180
+ q.breakable
181
+ q.group 2, "[", "]" do
182
+ q.text "%.2fdeg" % hue
183
+ q.fill_breakable
184
+ q.text "%.2f%%" % saturation
185
+ q.fill_breakable
186
+ q.text "%.2f%%" % luminosity
187
+ end
170
188
  end
171
189
 
172
- # Mix the mask colour (which will be converted to an HSL colour) with the
173
- # current colour at the stated mix percentage as a decimal value.
190
+ ##
191
+ # Mix the mask color (which will be converted to a \HSL color) with the current color
192
+ # at the stated fractional mix ratio (0.0..1.0).
174
193
  #
175
- # NOTE:: This differs from Color::RGB#mix_with.
176
- def mix_with(color, mix_percent = 0.5)
177
- v = to_a.zip(coerce(color).to_a).map { |(x, y)|
178
- ((y - x) * mix_percent) + x
179
- }
180
- self.class.from_fraction(*v)
181
- end
194
+ # This implementation differs from Color::RGB#mix_with.
195
+ #
196
+ # :call-seq:
197
+ # mix_with(mask, mix_ratio: 0.5)
198
+ def mix_with(mask, *args, **kwargs)
199
+ mix_ratio = normalize(kwargs[:mix_ratio] || args.first || 0.5)
182
200
 
183
- def to_a
184
- [ h, s, l ]
201
+ map_with(mask) { ((_2 - _1) * mix_ratio) + _1 }
185
202
  end
186
203
 
204
+ ##
205
+ def to_a = [hue, saturation, luminosity] # :nodoc:
206
+
207
+ ##
208
+ alias_method :deconstruct, :to_a
209
+
210
+ ##
211
+ def deconstruct_keys(_keys) = {h:, s:, l:, hue:, saturation:, luminance:} # :nodoc:
212
+
213
+ ##
214
+ def to_internal = [h, s, l] # :nodoc:
215
+
187
216
  private
188
217
 
189
- # This algorithm calculates based on a mixture of the saturation and
190
- # luminance, and then takes the RGB values from the hue + 1/3, hue, and
191
- # hue - 1/3 positions in a circular representation of colour divided into
192
- # four parts (confusing, I know, but it's the way that it works). See
193
- # #hue_to_rgb for more information.
194
- def compute_fvd_rgb
218
+ ##
219
+ # This algorithm calculates based on a mixture of the saturation and luminance, and then
220
+ # takes the RGB values from the hue + 1/3, hue, and hue - 1/3 positions in a circular
221
+ # representation of color divided into four parts (confusing, I know, but it's the way
222
+ # that it works). See #hue_to_rgb for more information.
223
+ def compute_fvd_rgb # :nodoc:
195
224
  t1, t2 = fvd_mix_sat_lum
196
- [ h + (1 / 3.0), h, h - (1 / 3.0) ].map { |v|
225
+ [h + (1 / 3.0), h, h - (1 / 3.0)].map { |v|
197
226
  hue_to_rgb(rotate_hue(v), t1, t2)
198
227
  }
199
228
  end
200
229
 
201
- # Mix saturation and luminance for use in hue_to_rgb. The base value is
202
- # different depending on whether luminance is <= 50% or > 50%.
203
- def fvd_mix_sat_lum
204
- t = if Color.near_zero_or_less?(l - 0.5)
205
- l * (1.0 + s.to_f)
206
- else
207
- l + s - (l * s.to_f)
208
- end
209
- [ 2.0 * l - t, t ]
230
+ ##
231
+ # Mix saturation and luminance for use in hue_to_rgb. The base value is different
232
+ # depending on whether luminance is <= 50% or > 50%.
233
+ def fvd_mix_sat_lum # :nodoc:
234
+ t = if near_zero_or_less?(l - 0.5)
235
+ l * (1.0 + s.to_f)
236
+ else
237
+ l + s - (l * s.to_f)
238
+ end
239
+ [2.0 * l - t, t]
210
240
  end
211
241
 
212
- # In HSL, hues are referenced as degrees in a colour circle. The flow
213
- # itself is endless; therefore, we can rotate around. The only thing our
214
- # implementation restricts is that you should not be > 1.0.
215
- def rotate_hue(h)
216
- h += 1.0 if Color.near_zero_or_less?(h)
217
- h -= 1.0 if Color.near_one_or_more?(h)
242
+ ##
243
+ # In \HSL, hues are referenced as degrees in a color circle. The flow itself is endless;
244
+ # therefore, we can rotate around. The only thing our implementation restricts is that
245
+ # you should not be > 1.0.
246
+ def rotate_hue(h) # :nodoc:
247
+ h += 1.0 if near_zero_or_less?(h)
248
+ h -= 1.0 if near_one_or_more?(h)
218
249
  h
219
250
  end
220
251
 
221
- # We calculate the interaction of the saturation/luminance mix (calculated
222
- # earlier) based on the position of the hue in the circular colour space
223
- # divided into quadrants. Our hue range is [0, 1), not [0, 360º).
252
+ ##
253
+ # We calculate the interaction of the saturation/luminance mix (calculated earlier)
254
+ # based on the position of the hue in the circular color space divided into quadrants.
255
+ # Our hue range is [0, 1), not [0, 360º).
224
256
  #
225
257
  # - The first quadrant covers the first 60º [0, 60º].
226
258
  # - The second quadrant covers the next 120º (60º, 180º].
227
259
  # - The third quadrant covers the next 60º (180º, 240º].
228
260
  # - The fourth quadrant covers the final 120º (240º, 360º).
229
- def hue_to_rgb(h, t1, t2)
230
- if Color.near_zero_or_less?((6.0 * h) - 1.0)
261
+ def hue_to_rgb(h, t1, t2) # :nodoc:
262
+ if near_zero_or_less?((6.0 * h) - 1.0)
231
263
  t1 + ((t2 - t1) * h * 6.0)
232
- elsif Color.near_zero_or_less?((2.0 * h) - 1.0)
264
+ elsif near_zero_or_less?((2.0 * h) - 1.0)
233
265
  t2
234
- elsif Color.near_zero_or_less?((3.0 * h) - 2.0)
266
+ elsif near_zero_or_less?((3.0 * h) - 2.0)
235
267
  t1 + (t2 - t1) * ((2 / 3.0) - h) * 6.0
236
268
  else
237
269
  t1